@lumikmz/kmz 0.2.0
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/LICENSE +21 -0
- package/README.md +540 -0
- package/dist/index.d.mts +789 -0
- package/dist/index.mjs +779 -0
- package/package.json +24 -0
- package/src/index.ts +3 -0
- package/src/plan/geometry.ts +109 -0
- package/src/plan/index.ts +31 -0
- package/src/plan/plan-mapping-strip.ts +84 -0
- package/src/plan/plan-mapping2d.ts +128 -0
- package/src/plan/plan-mapping3d.ts +188 -0
- package/src/plan/plan-waypoint.ts +80 -0
- package/src/plan/plan.test.ts +96 -0
- package/src/types/action.ts +253 -0
- package/src/types/branded.ts +20 -0
- package/src/types/drone-payload.ts +59 -0
- package/src/types/enums.ts +97 -0
- package/src/types/errors.ts +19 -0
- package/src/types/index.ts +10 -0
- package/src/types/mission-config.ts +58 -0
- package/src/types/placemark.ts +96 -0
- package/src/types/template.ts +170 -0
- package/src/types/waylines.ts +32 -0
- package/src/types/waypoint-params.ts +78 -0
- package/src/validate/index.ts +25 -0
- package/src/validate/validate-mission-config.ts +48 -0
- package/src/validate/validate-template.ts +101 -0
- package/src/validate/validate-waylines.ts +59 -0
- package/tsconfig.json +4 -0
- package/vite.config.ts +9 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Degrees, LngLat, Meters, MetersPerSecond } from "./branded.js";
|
|
2
|
+
import type { ActionGroup } from "./action.js";
|
|
3
|
+
import type { ExecuteHeightMode } from "./enums.js";
|
|
4
|
+
import type { WaypointHeadingParam, WaypointTurnParam } from "./waypoint-params.js";
|
|
5
|
+
|
|
6
|
+
// ─── Template Placemark ──────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `Placemark` inside a `waypoint`-type Folder of template.kml.
|
|
10
|
+
*
|
|
11
|
+
* Note coordinate order: `Point.coordinates` is `lon,lat` per KML (no altitude).
|
|
12
|
+
*/
|
|
13
|
+
export interface TemplateWaypointPlacemark {
|
|
14
|
+
Point: { coordinates: LngLat };
|
|
15
|
+
index: number;
|
|
16
|
+
|
|
17
|
+
useGlobalHeight: boolean;
|
|
18
|
+
/** Required iff `useGlobalHeight === false`. WGS84 ellipsoid. */
|
|
19
|
+
ellipsoidHeight?: Meters;
|
|
20
|
+
/** Required iff `useGlobalHeight === false`. Interpretation per Folder `heightMode`. */
|
|
21
|
+
height?: Meters;
|
|
22
|
+
|
|
23
|
+
useGlobalSpeed: boolean;
|
|
24
|
+
/** Required iff `useGlobalSpeed === false`. Range `[1, 15]`. */
|
|
25
|
+
waypointSpeed?: MetersPerSecond;
|
|
26
|
+
|
|
27
|
+
useGlobalHeadingParam: boolean;
|
|
28
|
+
/** Required iff `useGlobalHeadingParam === false`. */
|
|
29
|
+
waypointHeadingParam?: WaypointHeadingParam;
|
|
30
|
+
|
|
31
|
+
useGlobalTurnParam: boolean;
|
|
32
|
+
/** Required iff `useGlobalTurnParam === false`. */
|
|
33
|
+
waypointTurnParam?: WaypointTurnParam;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Required iff inner `waypointTurnMode` is one of
|
|
37
|
+
* `toPointAndStopWithContinuityCurvature` or `toPointAndPassWithContinuityCurvature`.
|
|
38
|
+
*/
|
|
39
|
+
useStraightLine?: boolean;
|
|
40
|
+
|
|
41
|
+
/** Required iff Folder `gimbalPitchMode === "usePointSetting"`. */
|
|
42
|
+
gimbalPitchAngle?: Degrees;
|
|
43
|
+
|
|
44
|
+
/** Optional on M30/M30T, M3D/M3TD, M4D/M4TD, M4E/M4T. */
|
|
45
|
+
isRisky?: boolean;
|
|
46
|
+
|
|
47
|
+
actionGroup?: ActionGroup[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `Placemark` inside a mapping-type Folder (mapping2d / mapping3d).
|
|
52
|
+
* Holds the survey polygon. Folder-level fields carry shooting params.
|
|
53
|
+
*/
|
|
54
|
+
export interface TemplatePolygonPlacemark {
|
|
55
|
+
Polygon: {
|
|
56
|
+
outerBoundaryIs: {
|
|
57
|
+
LinearRing: {
|
|
58
|
+
/** Space-separated `lon,lat,alt` triples. */
|
|
59
|
+
coordinates: string;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Placemark for `mappingStrip` Folder — uses a LineString. */
|
|
66
|
+
export interface TemplateLineStringPlacemark {
|
|
67
|
+
LineString: {
|
|
68
|
+
/** Space-separated `lon,lat,alt` triples. Alt consumed only when `stripUseTemplateAltitude === true`. */
|
|
69
|
+
coordinates: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Waylines Placemark ──────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `Placemark` inside a Folder of waylines.wpml.
|
|
77
|
+
*
|
|
78
|
+
* The spec's `useGlobalXxx` switches are template-only — in waylines.wpml the
|
|
79
|
+
* params are inlined unconditionally, so they are required here.
|
|
80
|
+
*/
|
|
81
|
+
export interface WaylinesPlacemark {
|
|
82
|
+
Point: { coordinates: LngLat };
|
|
83
|
+
index: number;
|
|
84
|
+
/** Interpretation per Folder `executeHeightMode`. */
|
|
85
|
+
executeHeight: Meters;
|
|
86
|
+
/** Range `[1, 15]`. */
|
|
87
|
+
waypointSpeed: MetersPerSecond;
|
|
88
|
+
waypointHeadingParam: WaypointHeadingParam;
|
|
89
|
+
waypointTurnParam: WaypointTurnParam;
|
|
90
|
+
useStraightLine?: boolean;
|
|
91
|
+
isRisky?: boolean;
|
|
92
|
+
actionGroup?: ActionGroup[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Re-export for convenience; folder-level executeHeightMode lives here. */
|
|
96
|
+
export type { ExecuteHeightMode };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { Degrees, Meters, MetersPerSecond } from "./branded.js";
|
|
2
|
+
import type { MissionConfig, PayloadParam } from "./mission-config.js";
|
|
3
|
+
import type { GimbalPitchMode, ShootType, WaypointTurnMode } from "./enums.js";
|
|
4
|
+
import type {
|
|
5
|
+
MappingHeadingParam,
|
|
6
|
+
Overlap,
|
|
7
|
+
WaylineCoordinateSysParam,
|
|
8
|
+
WaypointHeadingParam,
|
|
9
|
+
} from "./waypoint-params.js";
|
|
10
|
+
import type {
|
|
11
|
+
TemplateLineStringPlacemark,
|
|
12
|
+
TemplatePolygonPlacemark,
|
|
13
|
+
TemplateWaypointPlacemark,
|
|
14
|
+
} from "./placemark.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `template.kml` document. Mirrors `<kml><Document>` structure.
|
|
18
|
+
* See `docs/kmz/template-kml.html`.
|
|
19
|
+
*/
|
|
20
|
+
export type Template =
|
|
21
|
+
| WaypointTemplate
|
|
22
|
+
| Mapping2dTemplate
|
|
23
|
+
| Mapping3dTemplate
|
|
24
|
+
| MappingStripTemplate;
|
|
25
|
+
|
|
26
|
+
interface TemplateDocumentBase {
|
|
27
|
+
/** Unix milliseconds. */
|
|
28
|
+
author?: string;
|
|
29
|
+
createTime?: number;
|
|
30
|
+
updateTime?: number;
|
|
31
|
+
missionConfig: MissionConfig;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── waypoint ────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export interface WaypointTemplate extends TemplateDocumentBase {
|
|
37
|
+
Folder: WaypointFolder;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WaypointFolder {
|
|
41
|
+
templateType: "waypoint";
|
|
42
|
+
/** Range `[0, 65535]`. */
|
|
43
|
+
templateId: number;
|
|
44
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
45
|
+
/** Range `[1, 15]` m/s. */
|
|
46
|
+
autoFlightSpeed: MetersPerSecond;
|
|
47
|
+
payloadParam?: PayloadParam;
|
|
48
|
+
|
|
49
|
+
globalWaypointTurnMode: WaypointTurnMode;
|
|
50
|
+
/**
|
|
51
|
+
* Required iff `globalWaypointTurnMode` is one of
|
|
52
|
+
* `toPointAndStopWithContinuityCurvature` or `toPointAndPassWithContinuityCurvature`.
|
|
53
|
+
*/
|
|
54
|
+
globalUseStraightLine?: boolean;
|
|
55
|
+
/** `manual` | `usePointSetting`. */
|
|
56
|
+
gimbalPitchMode: GimbalPitchMode;
|
|
57
|
+
/** Relative to takeoff point. */
|
|
58
|
+
globalHeight: Meters;
|
|
59
|
+
globalWaypointHeadingParam: WaypointHeadingParam;
|
|
60
|
+
|
|
61
|
+
Placemark: TemplateWaypointPlacemark[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── mapping2d ───────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
export interface Mapping2dTemplate extends TemplateDocumentBase {
|
|
67
|
+
Folder: Mapping2dFolder;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Mapping2dFolder {
|
|
71
|
+
templateType: "mapping2d";
|
|
72
|
+
templateId: number;
|
|
73
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
74
|
+
autoFlightSpeed: MetersPerSecond;
|
|
75
|
+
payloadParam?: PayloadParam;
|
|
76
|
+
|
|
77
|
+
/** M300/M350 only. */
|
|
78
|
+
caliFlightEnable?: boolean;
|
|
79
|
+
elevationOptimizeEnable: boolean;
|
|
80
|
+
/** M300/M350+P1, M3E/M3T/M3M, M3D. */
|
|
81
|
+
smartObliqueEnable?: boolean;
|
|
82
|
+
smartObliqueGimbalPitch?: Degrees;
|
|
83
|
+
|
|
84
|
+
shootType: ShootType;
|
|
85
|
+
/** `[0, 360]`. */
|
|
86
|
+
direction: Degrees;
|
|
87
|
+
/** Outward survey-area margin, meters. */
|
|
88
|
+
margin: number;
|
|
89
|
+
overlap: Overlap;
|
|
90
|
+
/** WGS84 ellipsoid. */
|
|
91
|
+
ellipsoidHeight: Meters;
|
|
92
|
+
/** Per Folder `heightMode`. */
|
|
93
|
+
height: Meters;
|
|
94
|
+
|
|
95
|
+
/** M3E/M3T/M3M. */
|
|
96
|
+
facadeWaylineEnable?: boolean;
|
|
97
|
+
|
|
98
|
+
/** M3E/M3T/M3M, M3D/M3TD, M4D/M4TD, M4E/M4T. */
|
|
99
|
+
mappingHeadingParam?: MappingHeadingParam;
|
|
100
|
+
/** M3E/M3T/M3M, M3D/M3TD, M4D/M4TD, M4E/M4T. */
|
|
101
|
+
gimbalPitchMode?: "manual" | "fixed";
|
|
102
|
+
/** Required iff `gimbalPitchMode === "fixed"`. `[-90, -30]`. */
|
|
103
|
+
gimbalPitchAngle?: Degrees;
|
|
104
|
+
/** M4E only. */
|
|
105
|
+
quickOrthoMappingEnable?: boolean;
|
|
106
|
+
/** Required iff `quickOrthoMappingEnable === true`. `[10, 30]`. */
|
|
107
|
+
quickOrthoMappingPitch?: Degrees;
|
|
108
|
+
|
|
109
|
+
Placemark: TemplatePolygonPlacemark;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── mapping3d (oblique photography) ─────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export interface Mapping3dTemplate extends TemplateDocumentBase {
|
|
115
|
+
Folder: Mapping3dFolder;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface Mapping3dFolder {
|
|
119
|
+
templateType: "mapping3d";
|
|
120
|
+
templateId: number;
|
|
121
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
122
|
+
autoFlightSpeed: MetersPerSecond;
|
|
123
|
+
payloadParam?: PayloadParam;
|
|
124
|
+
|
|
125
|
+
caliFlightEnable?: boolean;
|
|
126
|
+
inclinedGimbalPitch: Degrees;
|
|
127
|
+
/** Range `[1, 15]`. */
|
|
128
|
+
inclinedFlightSpeed: MetersPerSecond;
|
|
129
|
+
shootType: ShootType;
|
|
130
|
+
direction: Degrees;
|
|
131
|
+
margin: number;
|
|
132
|
+
overlap: Overlap;
|
|
133
|
+
ellipsoidHeight: Meters;
|
|
134
|
+
height: Meters;
|
|
135
|
+
|
|
136
|
+
Placemark: TemplatePolygonPlacemark;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── mappingStrip ────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
export interface MappingStripTemplate extends TemplateDocumentBase {
|
|
142
|
+
Folder: MappingStripFolder;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface MappingStripFolder {
|
|
146
|
+
templateType: "mappingStrip";
|
|
147
|
+
templateId: number;
|
|
148
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
149
|
+
autoFlightSpeed: MetersPerSecond;
|
|
150
|
+
payloadParam?: PayloadParam;
|
|
151
|
+
|
|
152
|
+
caliFlightEnable: boolean;
|
|
153
|
+
shootType: ShootType;
|
|
154
|
+
direction: Degrees;
|
|
155
|
+
/** Float meters (vs integer in mapping2d/3d). */
|
|
156
|
+
margin: number;
|
|
157
|
+
singleLineEnable: boolean;
|
|
158
|
+
cuttingDistance: number;
|
|
159
|
+
boundaryOptimEnable: boolean;
|
|
160
|
+
leftExtend: number;
|
|
161
|
+
rightExtend: number;
|
|
162
|
+
includeCenterEnable: boolean;
|
|
163
|
+
overlap: Overlap;
|
|
164
|
+
ellipsoidHeight: Meters;
|
|
165
|
+
height: Meters;
|
|
166
|
+
/** When true, altitudes are read from LineString triples. */
|
|
167
|
+
stripUseTemplateAltitude: boolean;
|
|
168
|
+
|
|
169
|
+
Placemark: TemplateLineStringPlacemark;
|
|
170
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MetersPerSecond } from "./branded.js";
|
|
2
|
+
import type { ActionGroup } from "./action.js";
|
|
3
|
+
import type { ExecuteHeightMode } from "./enums.js";
|
|
4
|
+
import type { MissionConfig } from "./mission-config.js";
|
|
5
|
+
import type { WaylinesPlacemark } from "./placemark.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `waylines.wpml` document. Mirrors `<kml><Document>` structure.
|
|
9
|
+
* See `docs/kmz/waylines-wpml.html`.
|
|
10
|
+
*
|
|
11
|
+
* Cardinality: `Folder` is `1..*` — `mapping3d` templates expand to 5 Folders
|
|
12
|
+
* (1 ortho + 4 oblique).
|
|
13
|
+
*/
|
|
14
|
+
export interface Waylines {
|
|
15
|
+
missionConfig: MissionConfig;
|
|
16
|
+
Folder: WaylinesFolder[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WaylinesFolder {
|
|
20
|
+
/** Links back to template Folder's `templateId`. */
|
|
21
|
+
templateId: number;
|
|
22
|
+
waylineId: number;
|
|
23
|
+
executeHeightMode: ExecuteHeightMode;
|
|
24
|
+
/** Range `[1, 15]`. */
|
|
25
|
+
autoFlightSpeed: MetersPerSecond;
|
|
26
|
+
/**
|
|
27
|
+
* Optional pre-wayline action group.
|
|
28
|
+
* Available on M30/M30T, M3E/M3T/M3M, M3D/M3TD, M4D/M4TD, M4E/M4T.
|
|
29
|
+
*/
|
|
30
|
+
startActionGroup?: ActionGroup;
|
|
31
|
+
Placemark: WaylinesPlacemark[];
|
|
32
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Degrees, LngLatHeight, Meters } from "./branded.js";
|
|
2
|
+
import type {
|
|
3
|
+
CoordinateMode,
|
|
4
|
+
MappingHeadingMode,
|
|
5
|
+
PositioningType,
|
|
6
|
+
TemplateHeightMode,
|
|
7
|
+
WaypointHeadingMode,
|
|
8
|
+
WaypointHeadingPathMode,
|
|
9
|
+
WaypointTurnMode,
|
|
10
|
+
} from "./enums.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `wpml:waypointHeadingParam` & `wpml:globalWaypointHeadingParam`.
|
|
14
|
+
* See `docs/kmz/common-element.html` §waypointHeadingParam.
|
|
15
|
+
*/
|
|
16
|
+
export interface WaypointHeadingParam {
|
|
17
|
+
waypointHeadingMode: WaypointHeadingMode;
|
|
18
|
+
/** Required iff `waypointHeadingMode === "smoothTransition"`. Range `[-180, 180]`. */
|
|
19
|
+
waypointHeadingAngle?: Degrees;
|
|
20
|
+
/** Required iff `waypointHeadingMode === "towardPOI"`. Z aim unsupported; set height to 0. */
|
|
21
|
+
waypointPoiPoint?: LngLatHeight;
|
|
22
|
+
waypointHeadingPathMode: WaypointHeadingPathMode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `wpml:waypointTurnParam`.
|
|
27
|
+
* See `docs/kmz/common-element.html` §waypointTurnParam.
|
|
28
|
+
*/
|
|
29
|
+
export interface WaypointTurnParam {
|
|
30
|
+
waypointTurnMode: WaypointTurnMode;
|
|
31
|
+
/**
|
|
32
|
+
* Required iff `waypointTurnMode === "coordinateTurn"`
|
|
33
|
+
* OR (`waypointTurnMode === "toPointAndPassWithContinuityCurvature"` AND useStraightLine = 1).
|
|
34
|
+
*/
|
|
35
|
+
waypointTurnDampingDist?: Meters;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `wpml:waylineCoordinateSysParam`.
|
|
40
|
+
* Mapping templates require globalShootHeight + surfaceFollowModeEnable.
|
|
41
|
+
*/
|
|
42
|
+
export interface WaylineCoordinateSysParam {
|
|
43
|
+
/** Currently only `"WGS84"` is allowed. */
|
|
44
|
+
coordinateMode: CoordinateMode;
|
|
45
|
+
heightMode: TemplateHeightMode;
|
|
46
|
+
positioningType?: PositioningType;
|
|
47
|
+
/** Required for mapping templates. */
|
|
48
|
+
globalShootHeight?: Meters;
|
|
49
|
+
/** Required for mapping templates. */
|
|
50
|
+
surfaceFollowModeEnable?: boolean;
|
|
51
|
+
/** Required iff `surfaceFollowModeEnable === true`. */
|
|
52
|
+
surfaceRelativeHeight?: Meters;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* `wpml:mappingHeadingParam` — only on `mapping2d` Folder
|
|
57
|
+
* (M3E/M3T/M3M, M3D/M3TD, M4D/M4TD, M4E/M4T).
|
|
58
|
+
*/
|
|
59
|
+
export interface MappingHeadingParam {
|
|
60
|
+
mappingHeadingMode: MappingHeadingMode;
|
|
61
|
+
/** Required iff `mappingHeadingMode === "fixed"`. Range `[0, 360]`. */
|
|
62
|
+
mappingHeadingAngle?: Degrees;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `wpml:overlap` — child of mapping2d / mapping3d / mappingStrip Folders.
|
|
67
|
+
* All fields individually optional in spec but at least one ortho pair is required in practice.
|
|
68
|
+
*/
|
|
69
|
+
export interface Overlap {
|
|
70
|
+
orthoLidarOverlapH?: number;
|
|
71
|
+
orthoLidarOverlapW?: number;
|
|
72
|
+
orthoCameraOverlapH?: number;
|
|
73
|
+
orthoCameraOverlapW?: number;
|
|
74
|
+
inclinedLidarOverlapH?: number;
|
|
75
|
+
inclinedLidarOverlapW?: number;
|
|
76
|
+
inclinedCameraOverlapH?: number;
|
|
77
|
+
inclinedCameraOverlapW?: number;
|
|
78
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Issue } from "../types/errors.js";
|
|
2
|
+
import type { Template } from "../types/template.js";
|
|
3
|
+
import type { Waylines } from "../types/waylines.js";
|
|
4
|
+
import { validateMissionConfig } from "./validate-mission-config.js";
|
|
5
|
+
import { validateTemplate } from "./validate-template.js";
|
|
6
|
+
import { validateWaylines } from "./validate-waylines.js";
|
|
7
|
+
|
|
8
|
+
export type { Issue } from "../types/errors.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Run semantic checks against a `Template` or `Waylines` document.
|
|
12
|
+
* Returns a list of issues (`severity: "error" | "warning"`). Empty array = valid.
|
|
13
|
+
*
|
|
14
|
+
* The function is non-throwing by design — callers decide how to react.
|
|
15
|
+
*/
|
|
16
|
+
export function validate(doc: Template | Waylines): Issue[] {
|
|
17
|
+
const issues: Issue[] = [];
|
|
18
|
+
issues.push(...validateMissionConfig(doc.missionConfig, "missionConfig"));
|
|
19
|
+
if ("Folder" in doc && Array.isArray(doc.Folder)) {
|
|
20
|
+
issues.push(...validateWaylines(doc as Waylines));
|
|
21
|
+
} else {
|
|
22
|
+
issues.push(...validateTemplate(doc as Template));
|
|
23
|
+
}
|
|
24
|
+
return issues;
|
|
25
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Issue } from "../types/errors.js";
|
|
2
|
+
import type { MissionConfig } from "../types/mission-config.js";
|
|
3
|
+
|
|
4
|
+
export function validateMissionConfig(config: MissionConfig, path: string): Issue[] {
|
|
5
|
+
const issues: Issue[] = [];
|
|
6
|
+
|
|
7
|
+
if (config.exitOnRCLost === "executeLostAction" && config.executeRCLostAction === undefined) {
|
|
8
|
+
issues.push({
|
|
9
|
+
path: `${path}.executeRCLostAction`,
|
|
10
|
+
message: "executeRCLostAction is required when exitOnRCLost === 'executeLostAction'",
|
|
11
|
+
severity: "error",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (config.globalTransitionalSpeed < 1 || config.globalTransitionalSpeed > 15) {
|
|
16
|
+
issues.push({
|
|
17
|
+
path: `${path}.globalTransitionalSpeed`,
|
|
18
|
+
message: `globalTransitionalSpeed must be in [1,15] m/s, got ${config.globalTransitionalSpeed}`,
|
|
19
|
+
severity: "error",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (config.globalRTHHeight < 2 || config.globalRTHHeight > 1500) {
|
|
24
|
+
issues.push({
|
|
25
|
+
path: `${path}.globalRTHHeight`,
|
|
26
|
+
message: `globalRTHHeight must be in [2,1500] m, got ${config.globalRTHHeight}`,
|
|
27
|
+
severity: "error",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (config.takeOffSecurityHeight < 1.2 || config.takeOffSecurityHeight > 1500) {
|
|
32
|
+
issues.push({
|
|
33
|
+
path: `${path}.takeOffSecurityHeight`,
|
|
34
|
+
message: `takeOffSecurityHeight must be in [1.2,1500] m, got ${config.takeOffSecurityHeight}`,
|
|
35
|
+
severity: "error",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (config.payloadInfo.length === 0) {
|
|
40
|
+
issues.push({
|
|
41
|
+
path: `${path}.payloadInfo`,
|
|
42
|
+
message: "payloadInfo must contain at least one payload",
|
|
43
|
+
severity: "warning",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return issues;
|
|
48
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Issue } from "../types/errors.js";
|
|
2
|
+
import type {
|
|
3
|
+
Mapping2dTemplate,
|
|
4
|
+
Mapping3dTemplate,
|
|
5
|
+
MappingStripTemplate,
|
|
6
|
+
Template,
|
|
7
|
+
WaypointTemplate,
|
|
8
|
+
} from "../types/template.js";
|
|
9
|
+
|
|
10
|
+
export function validateTemplate(template: Template): Issue[] {
|
|
11
|
+
const folder = template.Folder;
|
|
12
|
+
const issues: Issue[] = [];
|
|
13
|
+
const path = "Folder";
|
|
14
|
+
|
|
15
|
+
if (folder.templateId < 0 || folder.templateId > 65535) {
|
|
16
|
+
issues.push({
|
|
17
|
+
path: `${path}.templateId`,
|
|
18
|
+
message: `templateId must be in [0,65535], got ${folder.templateId}`,
|
|
19
|
+
severity: "error",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (folder.autoFlightSpeed < 1 || folder.autoFlightSpeed > 15) {
|
|
24
|
+
issues.push({
|
|
25
|
+
path: `${path}.autoFlightSpeed`,
|
|
26
|
+
message: `autoFlightSpeed must be in [1,15] m/s, got ${folder.autoFlightSpeed}`,
|
|
27
|
+
severity: "error",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (folder.templateType) {
|
|
32
|
+
case "waypoint":
|
|
33
|
+
issues.push(...validateWaypointFolder(template as WaypointTemplate, path));
|
|
34
|
+
break;
|
|
35
|
+
case "mapping2d":
|
|
36
|
+
issues.push(...validateMapping2dFolder(template as Mapping2dTemplate, path));
|
|
37
|
+
break;
|
|
38
|
+
case "mapping3d":
|
|
39
|
+
issues.push(...validateMapping3dFolder(template as Mapping3dTemplate, path));
|
|
40
|
+
break;
|
|
41
|
+
case "mappingStrip":
|
|
42
|
+
issues.push(...validateMappingStripFolder(template as MappingStripTemplate, path));
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return issues;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateWaypointFolder(t: WaypointTemplate, path: string): Issue[] {
|
|
50
|
+
const issues: Issue[] = [];
|
|
51
|
+
if (t.Folder.Placemark.length === 0) {
|
|
52
|
+
issues.push({
|
|
53
|
+
path: `${path}.Placemark`,
|
|
54
|
+
message: "waypoint template must have at least one Placemark",
|
|
55
|
+
severity: "error",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function validateMapping2dFolder(t: Mapping2dTemplate, path: string): Issue[] {
|
|
62
|
+
return validatePolygonRing(
|
|
63
|
+
t.Folder.Placemark.Polygon.outerBoundaryIs.LinearRing.coordinates,
|
|
64
|
+
`${path}.Placemark.Polygon`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validateMapping3dFolder(t: Mapping3dTemplate, path: string): Issue[] {
|
|
69
|
+
return validatePolygonRing(
|
|
70
|
+
t.Folder.Placemark.Polygon.outerBoundaryIs.LinearRing.coordinates,
|
|
71
|
+
`${path}.Placemark.Polygon`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function validateMappingStripFolder(t: MappingStripTemplate, path: string): Issue[] {
|
|
76
|
+
const tuples = t.Folder.Placemark.LineString.coordinates.trim().split(/\s+/);
|
|
77
|
+
if (tuples.length < 2) {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
path: `${path}.Placemark.LineString`,
|
|
81
|
+
message: `LineString must have at least 2 vertices, got ${tuples.length}`,
|
|
82
|
+
severity: "error",
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validatePolygonRing(coords: string, path: string): Issue[] {
|
|
90
|
+
const tuples = coords.trim().split(/\s+/);
|
|
91
|
+
if (tuples.length < 3) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
path,
|
|
95
|
+
message: `Polygon boundary must have at least 3 vertices, got ${tuples.length}`,
|
|
96
|
+
severity: "error",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Issue } from "../types/errors.js";
|
|
2
|
+
import type { Waylines, WaylinesFolder } from "../types/waylines.js";
|
|
3
|
+
|
|
4
|
+
export function validateWaylines(waylines: Waylines): Issue[] {
|
|
5
|
+
const issues: Issue[] = [];
|
|
6
|
+
|
|
7
|
+
if (waylines.Folder.length === 0) {
|
|
8
|
+
issues.push({
|
|
9
|
+
path: "Folder",
|
|
10
|
+
message: "Waylines must contain at least one Folder",
|
|
11
|
+
severity: "error",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
waylines.Folder.forEach((folder, i) => {
|
|
16
|
+
issues.push(...validateFolder(folder, `Folder[${i}]`));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return issues;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function validateFolder(folder: WaylinesFolder, path: string): Issue[] {
|
|
23
|
+
const issues: Issue[] = [];
|
|
24
|
+
|
|
25
|
+
if (folder.autoFlightSpeed < 1 || folder.autoFlightSpeed > 15) {
|
|
26
|
+
issues.push({
|
|
27
|
+
path: `${path}.autoFlightSpeed`,
|
|
28
|
+
message: `autoFlightSpeed must be in [1,15] m/s, got ${folder.autoFlightSpeed}`,
|
|
29
|
+
severity: "error",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (folder.Placemark.length === 0) {
|
|
34
|
+
issues.push({
|
|
35
|
+
path: `${path}.Placemark`,
|
|
36
|
+
message: "Folder must contain at least one Placemark",
|
|
37
|
+
severity: "error",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
folder.Placemark.forEach((p, i) => {
|
|
42
|
+
if (p.index !== i) {
|
|
43
|
+
issues.push({
|
|
44
|
+
path: `${path}.Placemark[${i}].index`,
|
|
45
|
+
message: `Placemark.index must equal its array position (expected ${i}, got ${p.index})`,
|
|
46
|
+
severity: "error",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (p.waypointSpeed < 1 || p.waypointSpeed > 15) {
|
|
50
|
+
issues.push({
|
|
51
|
+
path: `${path}.Placemark[${i}].waypointSpeed`,
|
|
52
|
+
message: `waypointSpeed must be in [1,15] m/s, got ${p.waypointSpeed}`,
|
|
53
|
+
severity: "error",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
package/tsconfig.json
ADDED