@lumikmz/kmz 0.6.0 → 0.6.2
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/dist/index.d.mts +6 -0
- package/dist/index.mjs +35 -17
- package/package.json +1 -1
- package/src/plan/index.ts +6 -0
- package/src/plan/plan-mapping2d.ts +23 -6
- package/src/plan/plan-mapping3d.ts +19 -3
- package/src/plan/plan-waypoint.ts +8 -3
- package/src/plan/plan.test.ts +179 -0
package/dist/index.d.mts
CHANGED
|
@@ -903,6 +903,12 @@ interface WaylinesFolder {
|
|
|
903
903
|
interface PlanOptions {
|
|
904
904
|
lineSpacing?: Meters;
|
|
905
905
|
shootInterval?: number;
|
|
906
|
+
resolveExecuteHeight?: (point: {
|
|
907
|
+
lng: number;
|
|
908
|
+
lat: number;
|
|
909
|
+
index: number;
|
|
910
|
+
waylineId: number;
|
|
911
|
+
}) => Meters;
|
|
906
912
|
}
|
|
907
913
|
/**
|
|
908
914
|
* Compile a high-level `Template` into executable `Waylines`.
|
package/dist/index.mjs
CHANGED
|
@@ -406,6 +406,14 @@ function heightModeToExecuteMode$3(folder) {
|
|
|
406
406
|
case "realTimeFollowSurface": return "realTimeFollowSurface";
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
|
+
function executeHeightFor$1(folder, coord, index, waylineId, options) {
|
|
410
|
+
if (folder.waylineCoordinateSysParam.heightMode !== "aboveGroundLevel") return folder.Placemark.height;
|
|
411
|
+
return options?.resolveExecuteHeight?.({
|
|
412
|
+
...coord,
|
|
413
|
+
index,
|
|
414
|
+
waylineId
|
|
415
|
+
}) ?? folder.Placemark.height;
|
|
416
|
+
}
|
|
409
417
|
/** Desired damping distance; clamped down when segments are short. */
|
|
410
418
|
const DESIRED_TURN_DAMPING_M = 10;
|
|
411
419
|
/** Never consume more than this fraction of either adjacent segment. */
|
|
@@ -613,7 +621,7 @@ const COMMON_WP_EXTRAS = {
|
|
|
613
621
|
* waypoint — it does NOT detour to the centre (that detour is elevation optimization,
|
|
614
622
|
* not smart-swing). Mutates `placemarks` (appends two waypoints).
|
|
615
623
|
*/
|
|
616
|
-
function appendElevationExcursion(placemarks, folder, origin, enuWps, shootInterval, startGroupId) {
|
|
624
|
+
function appendElevationExcursion(placemarks, folder, origin, enuWps, shootInterval, startGroupId, options) {
|
|
617
625
|
const lastNadirIdx = enuWps.length - 1;
|
|
618
626
|
const obliqueStartIdx = placemarks.length;
|
|
619
627
|
const obliqueEndIdx = obliqueStartIdx + 1;
|
|
@@ -658,7 +666,7 @@ function appendElevationExcursion(placemarks, folder, origin, enuWps, shootInter
|
|
|
658
666
|
placemarks.push({
|
|
659
667
|
Point: { coordinates: lastNadirCoord },
|
|
660
668
|
index: obliqueStartIdx,
|
|
661
|
-
executeHeight: folder
|
|
669
|
+
executeHeight: executeHeightFor$1(folder, lastNadirCoord, obliqueStartIdx, 0, options),
|
|
662
670
|
waypointSpeed: folder.autoFlightSpeed,
|
|
663
671
|
waypointHeadingParam: mappingHeadingParam(obliqueHeading, true),
|
|
664
672
|
waypointTurnParam: stopTurn(),
|
|
@@ -668,7 +676,7 @@ function appendElevationExcursion(placemarks, folder, origin, enuWps, shootInter
|
|
|
668
676
|
placemarks.push({
|
|
669
677
|
Point: { coordinates: centroidCoord },
|
|
670
678
|
index: obliqueEndIdx,
|
|
671
|
-
executeHeight: folder
|
|
679
|
+
executeHeight: executeHeightFor$1(folder, centroidCoord, obliqueEndIdx, 0, options),
|
|
672
680
|
waypointSpeed: folder.autoFlightSpeed,
|
|
673
681
|
waypointHeadingParam: mappingHeadingParam(0, false),
|
|
674
682
|
waypointTurnParam: stopTurn(),
|
|
@@ -688,7 +696,7 @@ function appendElevationExcursion(placemarks, folder, origin, enuWps, shootInter
|
|
|
688
696
|
* When `elevationOptimizeEnable` is set, a centroid excursion is appended (see
|
|
689
697
|
* `appendElevationExcursion`); otherwise the route ends at the last grid waypoint.
|
|
690
698
|
*/
|
|
691
|
-
function emitOrtho(folder, origin, enuWps, shootInterval) {
|
|
699
|
+
function emitOrtho(folder, origin, enuWps, shootInterval, options) {
|
|
692
700
|
const lastIndex = enuWps.length - 1;
|
|
693
701
|
const startActions = shootSectionStartActions(-90, shootInterval, 0, lastIndex, 0);
|
|
694
702
|
const stopGroup = shootSectionStopActions(lastIndex, startActions.nextGroupId);
|
|
@@ -699,7 +707,7 @@ function emitOrtho(folder, origin, enuWps, shootInterval) {
|
|
|
699
707
|
const placemark = {
|
|
700
708
|
Point: { coordinates: coord },
|
|
701
709
|
index,
|
|
702
|
-
executeHeight: folder
|
|
710
|
+
executeHeight: executeHeightFor$1(folder, coord, index, 0, options),
|
|
703
711
|
waypointSpeed: folder.autoFlightSpeed,
|
|
704
712
|
waypointHeadingParam: mappingHeadingParam(headingAngle, !isLast),
|
|
705
713
|
waypointTurnParam: index === 0 || isLast ? stopTurn() : {
|
|
@@ -712,7 +720,7 @@ function emitOrtho(folder, origin, enuWps, shootInterval) {
|
|
|
712
720
|
else if (isLast) placemark.actionGroup = [stopGroup];
|
|
713
721
|
return placemark;
|
|
714
722
|
});
|
|
715
|
-
if (folder.Placemark.elevationOptimizeEnable) appendElevationExcursion(placemarks, folder, origin, enuWps, shootInterval, startActions.nextGroupId + 1);
|
|
723
|
+
if (folder.Placemark.elevationOptimizeEnable) appendElevationExcursion(placemarks, folder, origin, enuWps, shootInterval, startActions.nextGroupId + 1, options);
|
|
716
724
|
return {
|
|
717
725
|
templateId: folder.templateId,
|
|
718
726
|
waylineId: 0,
|
|
@@ -726,7 +734,7 @@ function emitOrtho(folder, origin, enuWps, shootInterval) {
|
|
|
726
734
|
* Single-pass smart-oblique / quick-ortho (smartObliqueEnable=true):
|
|
727
735
|
* The aircraft flies one grid while the gimbal swings (startSmartOblique/stopSmartOblique).
|
|
728
736
|
*/
|
|
729
|
-
function emitSmartOblique(folder, origin, enuWps) {
|
|
737
|
+
function emitSmartOblique(folder, origin, enuWps, options) {
|
|
730
738
|
const lastIndex = enuWps.length - 1;
|
|
731
739
|
const pitch = folder.Placemark.smartObliqueGimbalPitch ?? degrees(-45);
|
|
732
740
|
const placemarks = enuWps.map((enu, index) => {
|
|
@@ -736,7 +744,7 @@ function emitSmartOblique(folder, origin, enuWps) {
|
|
|
736
744
|
const placemark = {
|
|
737
745
|
Point: { coordinates: coord },
|
|
738
746
|
index,
|
|
739
|
-
executeHeight: folder
|
|
747
|
+
executeHeight: executeHeightFor$1(folder, coord, index, 0, options),
|
|
740
748
|
waypointSpeed: folder.autoFlightSpeed,
|
|
741
749
|
waypointHeadingParam: mappingHeadingParam(headingAngle, !isLast),
|
|
742
750
|
waypointTurnParam: index === 0 || isLast ? stopTurn() : {
|
|
@@ -787,11 +795,11 @@ function planMapping2d(template, options) {
|
|
|
787
795
|
const shootInterval = options.shootInterval ?? 2;
|
|
788
796
|
if (folder.Placemark.smartObliqueEnable === true) return {
|
|
789
797
|
missionConfig: template.missionConfig,
|
|
790
|
-
Folder: [emitSmartOblique(folder, origin, enuWps)]
|
|
798
|
+
Folder: [emitSmartOblique(folder, origin, enuWps, options)]
|
|
791
799
|
};
|
|
792
800
|
return {
|
|
793
801
|
missionConfig: template.missionConfig,
|
|
794
|
-
Folder: [emitOrtho(folder, origin, enuWps, shootInterval)]
|
|
802
|
+
Folder: [emitOrtho(folder, origin, enuWps, shootInterval, options)]
|
|
795
803
|
};
|
|
796
804
|
}
|
|
797
805
|
//#endregion
|
|
@@ -875,18 +883,28 @@ function planMapping3d(template, options) {
|
|
|
875
883
|
headingMode: pass.headingMode,
|
|
876
884
|
headingAngle: degrees(wrap180(baseDirection + pass.headingOffsetDeg)),
|
|
877
885
|
gimbalPitch: pass.pitch === "nadir" ? degrees(-90) : inclined,
|
|
878
|
-
interval: pass.interval
|
|
886
|
+
interval: pass.interval,
|
|
887
|
+
options
|
|
879
888
|
});
|
|
880
889
|
})
|
|
881
890
|
};
|
|
882
891
|
}
|
|
892
|
+
function executeHeightFor(folder, coord, index, waylineId, options) {
|
|
893
|
+
if (folder.waylineCoordinateSysParam.heightMode !== "aboveGroundLevel") return folder.Placemark.height;
|
|
894
|
+
return options?.resolveExecuteHeight?.({
|
|
895
|
+
...coord,
|
|
896
|
+
index,
|
|
897
|
+
waylineId
|
|
898
|
+
}) ?? folder.Placemark.height;
|
|
899
|
+
}
|
|
883
900
|
function buildPass(folder, waylineId, origin, enuWps, cfg) {
|
|
884
901
|
const lastIndex = enuWps.length - 1;
|
|
885
902
|
const placemarks = enuWps.map((enu, index) => {
|
|
903
|
+
const coord = enuToLngLat(enu, origin);
|
|
886
904
|
const placemark = {
|
|
887
|
-
Point: { coordinates:
|
|
905
|
+
Point: { coordinates: coord },
|
|
888
906
|
index,
|
|
889
|
-
executeHeight: folder.
|
|
907
|
+
executeHeight: executeHeightFor(folder, coord, index, waylineId, cfg.options),
|
|
890
908
|
waypointSpeed: folder.Placemark.inclinedFlightSpeed,
|
|
891
909
|
waypointHeadingParam: {
|
|
892
910
|
waypointHeadingMode: cfg.headingMode,
|
|
@@ -1043,7 +1061,7 @@ function planWaypoint(template) {
|
|
|
1043
1061
|
const placemarks = folder.Placemark.map((p, i) => {
|
|
1044
1062
|
const heading = p.useGlobalHeadingParam || p.waypointHeadingParam === void 0 ? folder.globalWaypointHeadingParam : p.waypointHeadingParam;
|
|
1045
1063
|
const turn = p.useGlobalTurnParam || p.waypointTurnParam === void 0 ? { waypointTurnMode: folder.globalWaypointTurnMode } : p.waypointTurnParam;
|
|
1046
|
-
const executeHeight = p.useGlobalHeight || p.height === void 0 ? folder.globalHeight : p.height;
|
|
1064
|
+
const executeHeight = folder.waylineCoordinateSysParam.heightMode === "aboveGroundLevel" && p.ellipsoidHeight !== void 0 ? p.ellipsoidHeight : p.useGlobalHeight || p.height === void 0 ? folder.globalHeight : p.height;
|
|
1047
1065
|
const waypointSpeed = p.useGlobalSpeed || p.waypointSpeed === void 0 ? folder.autoFlightSpeed : p.waypointSpeed;
|
|
1048
1066
|
return {
|
|
1049
1067
|
Point: p.Point,
|
|
@@ -1072,13 +1090,13 @@ function planWaypoint(template) {
|
|
|
1072
1090
|
/**
|
|
1073
1091
|
* Map a template heightMode to a waylines executeHeightMode.
|
|
1074
1092
|
* - `EGM96` → `WGS84` (waylines spec doesn't expose EGM96; runtime resolves it).
|
|
1075
|
-
* - `aboveGroundLevel` → `
|
|
1093
|
+
* - `aboveGroundLevel` → `WGS84` (waypoint waylines use per-point ellipsoid heights).
|
|
1076
1094
|
*/
|
|
1077
1095
|
function heightModeToExecuteMode(mode) {
|
|
1078
1096
|
switch (mode) {
|
|
1079
|
-
case "EGM96":
|
|
1097
|
+
case "EGM96":
|
|
1098
|
+
case "aboveGroundLevel": return "WGS84";
|
|
1080
1099
|
case "relativeToStartPoint": return "relativeToStartPoint";
|
|
1081
|
-
case "aboveGroundLevel":
|
|
1082
1100
|
case "realTimeFollowSurface": return "realTimeFollowSurface";
|
|
1083
1101
|
}
|
|
1084
1102
|
}
|
package/package.json
CHANGED
package/src/plan/index.ts
CHANGED
|
@@ -28,6 +28,12 @@ import { planWaypoint } from "./plan-waypoint.js";
|
|
|
28
28
|
export interface PlanOptions {
|
|
29
29
|
lineSpacing?: Meters;
|
|
30
30
|
shootInterval?: number;
|
|
31
|
+
resolveExecuteHeight?: (point: {
|
|
32
|
+
lng: number;
|
|
33
|
+
lat: number;
|
|
34
|
+
index: number;
|
|
35
|
+
waylineId: number;
|
|
36
|
+
}) => Meters;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
const isWaypoint = (t: Template): t is WaypointTemplate => t.Folder.templateType === "waypoint";
|
|
@@ -68,6 +68,19 @@ function heightModeToExecuteMode(folder: Mapping2dFolder): ExecuteHeightMode {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function executeHeightFor(
|
|
72
|
+
folder: Mapping2dFolder,
|
|
73
|
+
coord: { lng: number; lat: number },
|
|
74
|
+
index: number,
|
|
75
|
+
waylineId: number,
|
|
76
|
+
options?: PlanOptions,
|
|
77
|
+
): Meters {
|
|
78
|
+
if (folder.waylineCoordinateSysParam.heightMode !== "aboveGroundLevel") {
|
|
79
|
+
return folder.Placemark.height;
|
|
80
|
+
}
|
|
81
|
+
return options?.resolveExecuteHeight?.({ ...coord, index, waylineId }) ?? folder.Placemark.height;
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
/** Desired damping distance; clamped down when segments are short. */
|
|
72
85
|
const DESIRED_TURN_DAMPING_M = 10;
|
|
73
86
|
|
|
@@ -295,6 +308,7 @@ function appendElevationExcursion(
|
|
|
295
308
|
enuWps: readonly EnuPoint[],
|
|
296
309
|
shootInterval: number,
|
|
297
310
|
startGroupId: number,
|
|
311
|
+
options?: PlanOptions,
|
|
298
312
|
): void {
|
|
299
313
|
const lastNadirIdx = enuWps.length - 1;
|
|
300
314
|
const obliqueStartIdx = placemarks.length;
|
|
@@ -352,7 +366,7 @@ function appendElevationExcursion(
|
|
|
352
366
|
placemarks.push({
|
|
353
367
|
Point: { coordinates: lastNadirCoord }, // same position as last nadir
|
|
354
368
|
index: obliqueStartIdx,
|
|
355
|
-
executeHeight: folder
|
|
369
|
+
executeHeight: executeHeightFor(folder, lastNadirCoord, obliqueStartIdx, 0, options),
|
|
356
370
|
waypointSpeed: folder.autoFlightSpeed,
|
|
357
371
|
waypointHeadingParam: mappingHeadingParam(obliqueHeading, true),
|
|
358
372
|
waypointTurnParam: stopTurn(),
|
|
@@ -363,7 +377,7 @@ function appendElevationExcursion(
|
|
|
363
377
|
placemarks.push({
|
|
364
378
|
Point: { coordinates: centroidCoord },
|
|
365
379
|
index: obliqueEndIdx,
|
|
366
|
-
executeHeight: folder
|
|
380
|
+
executeHeight: executeHeightFor(folder, centroidCoord, obliqueEndIdx, 0, options),
|
|
367
381
|
waypointSpeed: folder.autoFlightSpeed,
|
|
368
382
|
waypointHeadingParam: mappingHeadingParam(0, false),
|
|
369
383
|
waypointTurnParam: stopTurn(),
|
|
@@ -389,6 +403,7 @@ function emitOrtho(
|
|
|
389
403
|
origin: { lng: number; lat: number },
|
|
390
404
|
enuWps: readonly EnuPoint[],
|
|
391
405
|
shootInterval: number,
|
|
406
|
+
options?: PlanOptions,
|
|
392
407
|
): WaylinesFolder {
|
|
393
408
|
const lastIndex = enuWps.length - 1;
|
|
394
409
|
const startActions = shootSectionStartActions(-90, shootInterval, 0, lastIndex, 0);
|
|
@@ -401,7 +416,7 @@ function emitOrtho(
|
|
|
401
416
|
const placemark: WaylinesPlacemark = {
|
|
402
417
|
Point: { coordinates: coord },
|
|
403
418
|
index,
|
|
404
|
-
executeHeight: folder
|
|
419
|
+
executeHeight: executeHeightFor(folder, coord, index, 0, options),
|
|
405
420
|
waypointSpeed: folder.autoFlightSpeed,
|
|
406
421
|
waypointHeadingParam: mappingHeadingParam(headingAngle, !isLast),
|
|
407
422
|
waypointTurnParam:
|
|
@@ -429,6 +444,7 @@ function emitOrtho(
|
|
|
429
444
|
enuWps,
|
|
430
445
|
shootInterval,
|
|
431
446
|
startActions.nextGroupId + 1,
|
|
447
|
+
options,
|
|
432
448
|
);
|
|
433
449
|
}
|
|
434
450
|
|
|
@@ -450,6 +466,7 @@ function emitSmartOblique(
|
|
|
450
466
|
folder: Mapping2dFolder,
|
|
451
467
|
origin: { lng: number; lat: number },
|
|
452
468
|
enuWps: readonly EnuPoint[],
|
|
469
|
+
options?: PlanOptions,
|
|
453
470
|
): WaylinesFolder {
|
|
454
471
|
const lastIndex = enuWps.length - 1;
|
|
455
472
|
const pitch = folder.Placemark.smartObliqueGimbalPitch ?? degrees(-45);
|
|
@@ -461,7 +478,7 @@ function emitSmartOblique(
|
|
|
461
478
|
const placemark: WaylinesPlacemark = {
|
|
462
479
|
Point: { coordinates: coord },
|
|
463
480
|
index,
|
|
464
|
-
executeHeight: folder
|
|
481
|
+
executeHeight: executeHeightFor(folder, coord, index, 0, options),
|
|
465
482
|
waypointSpeed: folder.autoFlightSpeed,
|
|
466
483
|
waypointHeadingParam: mappingHeadingParam(headingAngle, !isLast),
|
|
467
484
|
waypointTurnParam:
|
|
@@ -539,13 +556,13 @@ export function planMapping2d(template: Mapping2dTemplate, options?: PlanOptions
|
|
|
539
556
|
if (folder.Placemark.smartObliqueEnable === true) {
|
|
540
557
|
return {
|
|
541
558
|
missionConfig: template.missionConfig,
|
|
542
|
-
Folder: [emitSmartOblique(folder, origin, enuWps)],
|
|
559
|
+
Folder: [emitSmartOblique(folder, origin, enuWps, options)],
|
|
543
560
|
};
|
|
544
561
|
}
|
|
545
562
|
// 正射 and 正射+智能摆拍 share the nadir grid; the centroid detour is driven by
|
|
546
563
|
// elevationOptimizeEnable inside emitOrtho, not by quickOrthoMappingEnable.
|
|
547
564
|
return {
|
|
548
565
|
missionConfig: template.missionConfig,
|
|
549
|
-
Folder: [emitOrtho(folder, origin, enuWps, shootInterval)],
|
|
566
|
+
Folder: [emitOrtho(folder, origin, enuWps, shootInterval, options)],
|
|
550
567
|
};
|
|
551
568
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { degrees } from "../types/branded.js";
|
|
1
|
+
import { degrees, type Meters } from "../types/branded.js";
|
|
2
2
|
import type { Action, GimbalRotateAction } from "../types/action.js";
|
|
3
3
|
import type { ExecuteHeightMode, WaypointHeadingMode } from "../types/enums.js";
|
|
4
4
|
import type { Mapping3dFolder, Mapping3dTemplate } from "../types/template.js";
|
|
@@ -131,6 +131,7 @@ export function planMapping3d(template: Mapping3dTemplate, options?: PlanOptions
|
|
|
131
131
|
headingAngle: degrees(wrap180(baseDirection + pass.headingOffsetDeg)),
|
|
132
132
|
gimbalPitch: pass.pitch === "nadir" ? degrees(-90) : inclined,
|
|
133
133
|
interval: pass.interval,
|
|
134
|
+
options,
|
|
134
135
|
});
|
|
135
136
|
}),
|
|
136
137
|
};
|
|
@@ -141,6 +142,20 @@ interface PassConfig {
|
|
|
141
142
|
headingAngle: ReturnType<typeof degrees>;
|
|
142
143
|
gimbalPitch: ReturnType<typeof degrees>;
|
|
143
144
|
interval: number;
|
|
145
|
+
options?: PlanOptions;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function executeHeightFor(
|
|
149
|
+
folder: Mapping3dFolder,
|
|
150
|
+
coord: { lng: number; lat: number },
|
|
151
|
+
index: number,
|
|
152
|
+
waylineId: number,
|
|
153
|
+
options?: PlanOptions,
|
|
154
|
+
): Meters {
|
|
155
|
+
if (folder.waylineCoordinateSysParam.heightMode !== "aboveGroundLevel") {
|
|
156
|
+
return folder.Placemark.height;
|
|
157
|
+
}
|
|
158
|
+
return options?.resolveExecuteHeight?.({ ...coord, index, waylineId }) ?? folder.Placemark.height;
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
function buildPass(
|
|
@@ -152,10 +167,11 @@ function buildPass(
|
|
|
152
167
|
): WaylinesFolder {
|
|
153
168
|
const lastIndex = enuWps.length - 1;
|
|
154
169
|
const placemarks: WaylinesPlacemark[] = enuWps.map((enu, index) => {
|
|
170
|
+
const coord = enuToLngLat(enu, origin);
|
|
155
171
|
const placemark: WaylinesPlacemark = {
|
|
156
|
-
Point: { coordinates:
|
|
172
|
+
Point: { coordinates: coord },
|
|
157
173
|
index,
|
|
158
|
-
executeHeight: folder.
|
|
174
|
+
executeHeight: executeHeightFor(folder, coord, index, waylineId, cfg.options),
|
|
159
175
|
waypointSpeed: folder.Placemark.inclinedFlightSpeed,
|
|
160
176
|
waypointHeadingParam: {
|
|
161
177
|
waypointHeadingMode: cfg.headingMode,
|
|
@@ -25,7 +25,12 @@ export function planWaypoint(template: WaypointTemplate): Waylines {
|
|
|
25
25
|
: p.waypointTurnParam;
|
|
26
26
|
|
|
27
27
|
const executeHeight =
|
|
28
|
-
|
|
28
|
+
folder.waylineCoordinateSysParam.heightMode === "aboveGroundLevel" &&
|
|
29
|
+
p.ellipsoidHeight !== undefined
|
|
30
|
+
? p.ellipsoidHeight
|
|
31
|
+
: p.useGlobalHeight || p.height === undefined
|
|
32
|
+
? folder.globalHeight
|
|
33
|
+
: p.height;
|
|
29
34
|
|
|
30
35
|
const waypointSpeed =
|
|
31
36
|
p.useGlobalSpeed || p.waypointSpeed === undefined ? folder.autoFlightSpeed : p.waypointSpeed;
|
|
@@ -58,17 +63,17 @@ export function planWaypoint(template: WaypointTemplate): Waylines {
|
|
|
58
63
|
/**
|
|
59
64
|
* Map a template heightMode to a waylines executeHeightMode.
|
|
60
65
|
* - `EGM96` → `WGS84` (waylines spec doesn't expose EGM96; runtime resolves it).
|
|
61
|
-
* - `aboveGroundLevel` → `
|
|
66
|
+
* - `aboveGroundLevel` → `WGS84` (waypoint waylines use per-point ellipsoid heights).
|
|
62
67
|
*/
|
|
63
68
|
function heightModeToExecuteMode(
|
|
64
69
|
mode: WaypointFolder["waylineCoordinateSysParam"]["heightMode"],
|
|
65
70
|
): ExecuteHeightMode {
|
|
66
71
|
switch (mode) {
|
|
67
72
|
case "EGM96":
|
|
73
|
+
case "aboveGroundLevel":
|
|
68
74
|
return "WGS84";
|
|
69
75
|
case "relativeToStartPoint":
|
|
70
76
|
return "relativeToStartPoint";
|
|
71
|
-
case "aboveGroundLevel":
|
|
72
77
|
case "realTimeFollowSurface":
|
|
73
78
|
return "realTimeFollowSurface";
|
|
74
79
|
}
|
package/src/plan/plan.test.ts
CHANGED
|
@@ -61,6 +61,52 @@ describe("plan", () => {
|
|
|
61
61
|
expect(waylines.missionConfig).toBe(template.missionConfig);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
it("waypoint aboveGroundLevel uses per-point ellipsoid heights as WGS84 executeHeight", () => {
|
|
65
|
+
const template: WaypointTemplate = {
|
|
66
|
+
missionConfig,
|
|
67
|
+
Folder: {
|
|
68
|
+
templateType: "waypoint",
|
|
69
|
+
templateId: 0,
|
|
70
|
+
waylineCoordinateSysParam: { coordinateMode: "WGS84", heightMode: "aboveGroundLevel" },
|
|
71
|
+
autoFlightSpeed: metersPerSecond(10),
|
|
72
|
+
globalWaypointTurnMode: "toPointAndStopWithDiscontinuityCurvature",
|
|
73
|
+
gimbalPitchMode: "manual",
|
|
74
|
+
globalHeight: meters(50),
|
|
75
|
+
globalWaypointHeadingParam: {
|
|
76
|
+
waypointHeadingMode: "followWayline",
|
|
77
|
+
waypointHeadingPathMode: "followBadArc",
|
|
78
|
+
},
|
|
79
|
+
Placemark: [
|
|
80
|
+
{
|
|
81
|
+
Point: { coordinates: { lng: 113.938, lat: 22.5397 } },
|
|
82
|
+
index: 0,
|
|
83
|
+
useGlobalHeight: false,
|
|
84
|
+
height: meters(80),
|
|
85
|
+
ellipsoidHeight: meters(180),
|
|
86
|
+
useGlobalSpeed: true,
|
|
87
|
+
useGlobalHeadingParam: true,
|
|
88
|
+
useGlobalTurnParam: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
Point: { coordinates: { lng: 113.939, lat: 22.5407 } },
|
|
92
|
+
index: 1,
|
|
93
|
+
useGlobalHeight: false,
|
|
94
|
+
height: meters(90),
|
|
95
|
+
ellipsoidHeight: meters(190),
|
|
96
|
+
useGlobalSpeed: true,
|
|
97
|
+
useGlobalHeadingParam: true,
|
|
98
|
+
useGlobalTurnParam: true,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const waylines = plan(template);
|
|
105
|
+
|
|
106
|
+
expect(waylines.Folder[0]!.executeHeightMode).toBe("WGS84");
|
|
107
|
+
expect(waylines.Folder[0]!.Placemark.map((p) => Number(p.executeHeight))).toEqual([180, 190]);
|
|
108
|
+
});
|
|
109
|
+
|
|
64
110
|
it("compiles a mapping2d template into a serpentine scan", () => {
|
|
65
111
|
const template: Mapping2dTemplate = {
|
|
66
112
|
missionConfig,
|
|
@@ -130,6 +176,89 @@ describe("plan", () => {
|
|
|
130
176
|
expect(plan(template, { lineSpacing: meters(30) }).Folder).toHaveLength(1);
|
|
131
177
|
});
|
|
132
178
|
|
|
179
|
+
it("mapping2d aboveGroundLevel uses per-waypoint HAE from resolveExecuteHeight", () => {
|
|
180
|
+
const template: Mapping2dTemplate = {
|
|
181
|
+
missionConfig,
|
|
182
|
+
Folder: {
|
|
183
|
+
templateType: "mapping2d",
|
|
184
|
+
templateId: 2,
|
|
185
|
+
waylineCoordinateSysParam: { coordinateMode: "WGS84", heightMode: "aboveGroundLevel" },
|
|
186
|
+
autoFlightSpeed: metersPerSecond(10),
|
|
187
|
+
Placemark: {
|
|
188
|
+
elevationOptimizeEnable: false,
|
|
189
|
+
shootType: "distance",
|
|
190
|
+
direction: degrees(0),
|
|
191
|
+
margin: 0,
|
|
192
|
+
overlap: { orthoCameraOverlapH: 80, orthoCameraOverlapW: 70 },
|
|
193
|
+
ellipsoidHeight: meters(150),
|
|
194
|
+
height: meters(100),
|
|
195
|
+
Polygon: {
|
|
196
|
+
outerBoundaryIs: {
|
|
197
|
+
LinearRing: {
|
|
198
|
+
coordinates:
|
|
199
|
+
"113.938,22.5397,0 113.940,22.5397,0 113.940,22.5407,0 113.938,22.5407,0 113.938,22.5397,0",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const calls: Array<{ lng: number; lat: number; index: number; waylineId: number }> = [];
|
|
208
|
+
const waylines = plan(template, {
|
|
209
|
+
lineSpacing: meters(45),
|
|
210
|
+
resolveExecuteHeight: (arg) => {
|
|
211
|
+
calls.push(arg);
|
|
212
|
+
return meters(300 + arg.index);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(calls).toHaveLength(waylines.Folder[0]!.Placemark.length);
|
|
217
|
+
expect(calls[0]!.waylineId).toBe(0);
|
|
218
|
+
expect(calls[0]!.index).toBe(0);
|
|
219
|
+
expect(calls[0]!.lng).toBe(waylines.Folder[0]!.Placemark[0]!.Point.coordinates.lng);
|
|
220
|
+
expect(calls[0]!.lat).toBe(waylines.Folder[0]!.Placemark[0]!.Point.coordinates.lat);
|
|
221
|
+
expect(waylines.Folder[0]!.executeHeightMode).toBe("realTimeFollowSurface");
|
|
222
|
+
expect(waylines.Folder[0]!.Placemark[0]!.executeHeight).toBe(300);
|
|
223
|
+
expect(waylines.Folder[0]!.Placemark[1]!.executeHeight).toBe(301);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("mapping2d realTimeFollowSurface keeps template height and does not call resolveExecuteHeight", () => {
|
|
227
|
+
const template: Mapping2dTemplate = {
|
|
228
|
+
missionConfig,
|
|
229
|
+
Folder: {
|
|
230
|
+
templateType: "mapping2d",
|
|
231
|
+
templateId: 2,
|
|
232
|
+
waylineCoordinateSysParam: { coordinateMode: "WGS84", heightMode: "realTimeFollowSurface" },
|
|
233
|
+
autoFlightSpeed: metersPerSecond(10),
|
|
234
|
+
Placemark: {
|
|
235
|
+
elevationOptimizeEnable: false,
|
|
236
|
+
shootType: "distance",
|
|
237
|
+
direction: degrees(0),
|
|
238
|
+
margin: 0,
|
|
239
|
+
overlap: { orthoCameraOverlapH: 80, orthoCameraOverlapW: 70 },
|
|
240
|
+
ellipsoidHeight: meters(150),
|
|
241
|
+
height: meters(100),
|
|
242
|
+
Polygon: {
|
|
243
|
+
outerBoundaryIs: {
|
|
244
|
+
LinearRing: {
|
|
245
|
+
coordinates:
|
|
246
|
+
"113.938,22.5397,0 113.940,22.5397,0 113.940,22.5407,0 113.938,22.5407,0 113.938,22.5397,0",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const waylines = plan(template, {
|
|
255
|
+
lineSpacing: meters(45),
|
|
256
|
+
resolveExecuteHeight: () => meters(300),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(waylines.Folder[0]!.Placemark[0]!.executeHeight).toBe(100);
|
|
260
|
+
});
|
|
261
|
+
|
|
133
262
|
it("mapping2d ortho: grid density follows provided lineSpacing", () => {
|
|
134
263
|
const template: Mapping2dTemplate = {
|
|
135
264
|
missionConfig,
|
|
@@ -347,4 +476,54 @@ describe("plan", () => {
|
|
|
347
476
|
// lines than the same-axis nadir pass, despite covering the same width.
|
|
348
477
|
expect(wl.Folder[1]!.Placemark.length).toBeLessThan(wl.Folder[0]!.Placemark.length);
|
|
349
478
|
});
|
|
479
|
+
|
|
480
|
+
it("mapping3d aboveGroundLevel uses per-waypoint HAE from resolveExecuteHeight for each pass", () => {
|
|
481
|
+
const template: Mapping3dTemplate = {
|
|
482
|
+
missionConfig,
|
|
483
|
+
Folder: {
|
|
484
|
+
templateType: "mapping3d",
|
|
485
|
+
templateId: 0,
|
|
486
|
+
waylineCoordinateSysParam: { coordinateMode: "WGS84", heightMode: "aboveGroundLevel" },
|
|
487
|
+
autoFlightSpeed: metersPerSecond(10),
|
|
488
|
+
Placemark: {
|
|
489
|
+
inclinedGimbalPitch: degrees(-45),
|
|
490
|
+
inclinedFlightSpeed: metersPerSecond(10),
|
|
491
|
+
shootType: "time",
|
|
492
|
+
direction: degrees(0),
|
|
493
|
+
margin: 0,
|
|
494
|
+
overlap: { orthoCameraOverlapH: 80, orthoCameraOverlapW: 70 },
|
|
495
|
+
ellipsoidHeight: meters(150),
|
|
496
|
+
height: meters(100),
|
|
497
|
+
Polygon: {
|
|
498
|
+
outerBoundaryIs: {
|
|
499
|
+
LinearRing: {
|
|
500
|
+
coordinates:
|
|
501
|
+
"113.9380,22.5397,0 113.9460,22.5397,0 113.9460,22.5417,0 113.9380,22.5417,0 113.9380,22.5397,0",
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const calls: Array<{ lng: number; lat: number; index: number; waylineId: number }> = [];
|
|
510
|
+
const waylines = plan(template, {
|
|
511
|
+
lineSpacing: meters(45),
|
|
512
|
+
resolveExecuteHeight: (arg) => {
|
|
513
|
+
calls.push(arg);
|
|
514
|
+
return meters(500 + arg.waylineId * 100 + arg.index);
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const placemarkCount = waylines.Folder.reduce(
|
|
519
|
+
(sum, folder) => sum + folder.Placemark.length,
|
|
520
|
+
0,
|
|
521
|
+
);
|
|
522
|
+
expect(calls).toHaveLength(placemarkCount);
|
|
523
|
+
expect(waylines.Folder.map((folder) => folder.Placemark[0]!.executeHeight)).toEqual([
|
|
524
|
+
500, 600, 700, 800, 900,
|
|
525
|
+
]);
|
|
526
|
+
expect(calls.some((call) => call.waylineId === 4 && call.index === 0)).toBe(true);
|
|
527
|
+
expect(calls.every((call) => "heightMode" in call === false)).toBe(true);
|
|
528
|
+
});
|
|
350
529
|
});
|