@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zhaoyang Yuan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# @lumikmz/kmz
|
|
2
|
+
|
|
3
|
+
Spec-aligned TypeScript types for DJI KMZ/WPML missions, plus the `plan()` algorithm that compiles high-level templates into executable waylines and a `validate()` checker. Pure JavaScript — no WASM, no Rust, no file I/O.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @lumikmz/kmz
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
- [Design philosophy](#design-philosophy)
|
|
12
|
+
- [Public API](#public-api)
|
|
13
|
+
- [`plan(template)`](#plantemplate--waylines)
|
|
14
|
+
- [`validate(doc)`](#validatedoc--issue)
|
|
15
|
+
- [Type reference](#type-reference)
|
|
16
|
+
- [Document roots](#document-roots-template--waylines)
|
|
17
|
+
- [MissionConfig](#missionconfig)
|
|
18
|
+
- [Folder types per templateType](#folder-types-per-templatetype)
|
|
19
|
+
- [Placemark](#placemark)
|
|
20
|
+
- [WaypointHeadingParam / WaypointTurnParam](#waypointheadingparam--waypointturnparam)
|
|
21
|
+
- [ActionGroup, Action, ActionTrigger](#actiongroup-action-actiontrigger)
|
|
22
|
+
- [Branded primitives](#branded-primitives)
|
|
23
|
+
- [Enums](#enums)
|
|
24
|
+
- [Drone & payload constants](#drone--payload-constants)
|
|
25
|
+
- [How `plan()` dispatches](#how-plan-dispatches)
|
|
26
|
+
|
|
27
|
+
## Design philosophy
|
|
28
|
+
|
|
29
|
+
The type definitions are a **literal 1:1 mirror of the DJI WPML XML spec**, minus the `wpml:` prefix and with primitive values typed (numbers stay numbers, booleans stay booleans, `LngLat` is an object, not a `"lng,lat"` string). When the spec says `<wpml:Folder><wpml:Placemark><Point><coordinates>`, the TypeScript reads `Folder.Placemark.Point.coordinates`. When the spec spells something `visable` or `followBadArc`, the type literal does too — those misspellings are required by the DJI runtime.
|
|
30
|
+
|
|
31
|
+
You can author a `Template` by reading [`docs/kmz/template-kml.html`](../../docs/kmz/template-kml.html) and writing the object directly. No factory functions are needed (and none are provided).
|
|
32
|
+
|
|
33
|
+
## Public API
|
|
34
|
+
|
|
35
|
+
### `plan(template) → Waylines`
|
|
36
|
+
|
|
37
|
+
Compile a `Template` into the executable `Waylines` shape that DJI's flight controller expects. Pure computation — no I/O, no side effects.
|
|
38
|
+
|
|
39
|
+
Dispatch is by `template.Folder.templateType`:
|
|
40
|
+
|
|
41
|
+
| templateType | Algorithm |
|
|
42
|
+
| --------------- | ---------------------------------------------------------------------------------- |
|
|
43
|
+
| `waypoint` | One Folder. Each template Placemark → one wayline Placemark, resolving `useGlobalXxx` flags against folder defaults. |
|
|
44
|
+
| `mapping2d` | One Folder. Boustrophedon (serpentine) scan over the Polygon at `folder.height`, line spacing computed from `overlap.orthoCameraOverlapW`. Per-segment `takePhoto` between adjacent points. |
|
|
45
|
+
| `mapping3d` | **Five Folders** — one nadir ortho pass + four oblique passes (front/back/left/right) at `inclinedGimbalPitch`. Photos timed via `multipleTiming` trigger. |
|
|
46
|
+
| `mappingStrip` | One Folder. LineString vertices become waypoints; if `stripUseTemplateAltitude` is true, per-vertex altitude is read from the LineString triples, otherwise folder `height` is used. |
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { plan } from "@lumikmz/kmz";
|
|
50
|
+
|
|
51
|
+
const waylines = plan(template);
|
|
52
|
+
// waylines.Folder is WaylinesFolder[] — length depends on templateType
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `validate(doc) → Issue[]`
|
|
56
|
+
|
|
57
|
+
Run semantic checks against a `Template` or `Waylines`. Returns a list of `Issue`s; an empty array means valid. Non-throwing — callers decide how to react.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { validate } from "@lumikmz/kmz";
|
|
61
|
+
|
|
62
|
+
const issues = validate(template);
|
|
63
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
64
|
+
if (errors.length > 0) {
|
|
65
|
+
// refuse to ship
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Checks performed:
|
|
70
|
+
|
|
71
|
+
- `missionConfig`: `executeRCLostAction` required when `exitOnRCLost === "executeLostAction"`; speed ranges; `payloadInfo` non-empty (warning).
|
|
72
|
+
- Template: `templateId` ∈ `[0, 65535]`, `autoFlightSpeed` ∈ `[1, 15]`, waypoint Folder has ≥1 Placemark, mapping Folders have ≥3 polygon vertices, mappingStrip ≥2 LineString vertices.
|
|
73
|
+
- Waylines: per-Placemark `index` monotonicity, per-Folder Placemark count ≥1, `waypointSpeed` ranges.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
interface Issue {
|
|
77
|
+
path: string; // dot-path to the offending field, e.g. "Folder.Placemark[2].waypointSpeed"
|
|
78
|
+
message: string;
|
|
79
|
+
severity: "error" | "warning";
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Type reference
|
|
84
|
+
|
|
85
|
+
### Document roots: `Template` & `Waylines`
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
type Template =
|
|
89
|
+
| WaypointTemplate
|
|
90
|
+
| Mapping2dTemplate
|
|
91
|
+
| Mapping3dTemplate
|
|
92
|
+
| MappingStripTemplate;
|
|
93
|
+
|
|
94
|
+
interface TemplateDocumentBase {
|
|
95
|
+
author?: string;
|
|
96
|
+
createTime?: number; // Unix ms
|
|
97
|
+
updateTime?: number; // Unix ms
|
|
98
|
+
missionConfig: MissionConfig;
|
|
99
|
+
// Folder is added per variant
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface Waylines {
|
|
103
|
+
missionConfig: MissionConfig;
|
|
104
|
+
Folder: WaylinesFolder[]; // 1..* — mapping3d expands to 5
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface WaylinesFolder {
|
|
108
|
+
templateId: number;
|
|
109
|
+
waylineId: number;
|
|
110
|
+
executeHeightMode: ExecuteHeightMode;
|
|
111
|
+
autoFlightSpeed: MetersPerSecond;
|
|
112
|
+
startActionGroup?: ActionGroup;
|
|
113
|
+
Placemark: WaylinesPlacemark[];
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `MissionConfig`
|
|
118
|
+
|
|
119
|
+
Lives at the top level of both `template.kml` and `waylines.wpml`.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
interface MissionConfig {
|
|
123
|
+
flyToWaylineMode: "safely" | "pointToPoint";
|
|
124
|
+
finishAction: "goHome" | "noAction" | "autoLand" | "gotoFirstWaypoint";
|
|
125
|
+
exitOnRCLost: "goContinue" | "executeLostAction";
|
|
126
|
+
executeRCLostAction?: "goBack" | "landing" | "hover"; // required iff exitOnRCLost === "executeLostAction"
|
|
127
|
+
takeOffSecurityHeight: Meters;
|
|
128
|
+
takeOffRefPoint?: LngLatHeight;
|
|
129
|
+
takeOffRefPointAGLHeight?: Meters;
|
|
130
|
+
globalTransitionalSpeed: MetersPerSecond; // [1, 15]
|
|
131
|
+
globalRTHHeight: Meters; // [2, 1500]
|
|
132
|
+
droneInfo: DroneInfo;
|
|
133
|
+
payloadInfo: PayloadInfo[];
|
|
134
|
+
autoRerouteInfo?: AutoRerouteInfo; // M3D/M3TD, M4D/M4TD, M4E/M4T only
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface DroneInfo {
|
|
138
|
+
droneEnumValue: number;
|
|
139
|
+
droneSubEnumValue?: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface PayloadInfo {
|
|
143
|
+
payloadEnumValue: number;
|
|
144
|
+
payloadPositionIndex: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface AutoRerouteInfo {
|
|
148
|
+
missionAutoRerouteMode: boolean;
|
|
149
|
+
transitionalAutoRerouteMode: boolean;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Folder types per `templateType`
|
|
154
|
+
|
|
155
|
+
Each Folder variant shares a base (`templateId`, `autoFlightSpeed`, `waylineCoordinateSysParam`, `payloadParam?`, `Placemark`) plus type-specific fields.
|
|
156
|
+
|
|
157
|
+
#### `WaypointFolder` (`templateType: "waypoint"`)
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
interface WaypointFolder {
|
|
161
|
+
templateType: "waypoint";
|
|
162
|
+
templateId: number;
|
|
163
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
164
|
+
autoFlightSpeed: MetersPerSecond;
|
|
165
|
+
payloadParam?: PayloadParam;
|
|
166
|
+
|
|
167
|
+
globalWaypointTurnMode: WaypointTurnMode;
|
|
168
|
+
globalUseStraightLine?: boolean; // required iff turnMode ∈ {ContinuityCurvature variants}
|
|
169
|
+
gimbalPitchMode: "manual" | "usePointSetting";
|
|
170
|
+
globalHeight: Meters; // relative to takeoff point
|
|
171
|
+
globalWaypointHeadingParam: WaypointHeadingParam;
|
|
172
|
+
|
|
173
|
+
Placemark: TemplateWaypointPlacemark[];
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### `Mapping2dFolder` (`templateType: "mapping2d"`)
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
interface Mapping2dFolder {
|
|
181
|
+
templateType: "mapping2d";
|
|
182
|
+
templateId: number;
|
|
183
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
184
|
+
autoFlightSpeed: MetersPerSecond;
|
|
185
|
+
payloadParam?: PayloadParam;
|
|
186
|
+
|
|
187
|
+
caliFlightEnable?: boolean; // M300/M350 only
|
|
188
|
+
elevationOptimizeEnable: boolean;
|
|
189
|
+
smartObliqueEnable?: boolean; // turns "ortho" into "smart oblique"
|
|
190
|
+
smartObliqueGimbalPitch?: Degrees;
|
|
191
|
+
|
|
192
|
+
shootType: "time" | "distance";
|
|
193
|
+
direction: Degrees; // [0, 360]
|
|
194
|
+
margin: number;
|
|
195
|
+
overlap: Overlap;
|
|
196
|
+
ellipsoidHeight: Meters;
|
|
197
|
+
height: Meters;
|
|
198
|
+
|
|
199
|
+
facadeWaylineEnable?: boolean; // M3E/M3T/M3M only
|
|
200
|
+
mappingHeadingParam?: MappingHeadingParam;
|
|
201
|
+
gimbalPitchMode?: "manual" | "fixed";
|
|
202
|
+
gimbalPitchAngle?: Degrees; // required iff gimbalPitchMode === "fixed"
|
|
203
|
+
quickOrthoMappingEnable?: boolean; // M4E only
|
|
204
|
+
quickOrthoMappingPitch?: Degrees;
|
|
205
|
+
|
|
206
|
+
Placemark: TemplatePolygonPlacemark;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### `Mapping3dFolder` (`templateType: "mapping3d"`)
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
interface Mapping3dFolder {
|
|
214
|
+
templateType: "mapping3d";
|
|
215
|
+
templateId: number;
|
|
216
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
217
|
+
autoFlightSpeed: MetersPerSecond;
|
|
218
|
+
payloadParam?: PayloadParam;
|
|
219
|
+
|
|
220
|
+
caliFlightEnable?: boolean;
|
|
221
|
+
inclinedGimbalPitch: Degrees; // pitch for the 4 oblique passes
|
|
222
|
+
inclinedFlightSpeed: MetersPerSecond; // [1, 15]
|
|
223
|
+
shootType: "time" | "distance";
|
|
224
|
+
direction: Degrees;
|
|
225
|
+
margin: number;
|
|
226
|
+
overlap: Overlap;
|
|
227
|
+
ellipsoidHeight: Meters;
|
|
228
|
+
height: Meters;
|
|
229
|
+
|
|
230
|
+
Placemark: TemplatePolygonPlacemark;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### `MappingStripFolder` (`templateType: "mappingStrip"`)
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
interface MappingStripFolder {
|
|
238
|
+
templateType: "mappingStrip";
|
|
239
|
+
templateId: number;
|
|
240
|
+
waylineCoordinateSysParam: WaylineCoordinateSysParam;
|
|
241
|
+
autoFlightSpeed: MetersPerSecond;
|
|
242
|
+
payloadParam?: PayloadParam;
|
|
243
|
+
|
|
244
|
+
caliFlightEnable: boolean;
|
|
245
|
+
shootType: "time" | "distance";
|
|
246
|
+
direction: Degrees;
|
|
247
|
+
margin: number; // float (vs integer in mapping2d/3d)
|
|
248
|
+
singleLineEnable: boolean;
|
|
249
|
+
cuttingDistance: number;
|
|
250
|
+
boundaryOptimEnable: boolean;
|
|
251
|
+
leftExtend: number;
|
|
252
|
+
rightExtend: number;
|
|
253
|
+
includeCenterEnable: boolean;
|
|
254
|
+
overlap: Overlap;
|
|
255
|
+
ellipsoidHeight: Meters;
|
|
256
|
+
height: Meters;
|
|
257
|
+
stripUseTemplateAltitude: boolean; // when true, alt is read from LineString triples
|
|
258
|
+
|
|
259
|
+
Placemark: TemplateLineStringPlacemark;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Placemark
|
|
264
|
+
|
|
265
|
+
Two Placemark shapes — one for `template.kml`, one for `waylines.wpml`.
|
|
266
|
+
|
|
267
|
+
#### Template `Placemark`
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
interface TemplateWaypointPlacemark {
|
|
271
|
+
Point: { coordinates: LngLat }; // KML order: lng,lat
|
|
272
|
+
index: number;
|
|
273
|
+
|
|
274
|
+
useGlobalHeight: boolean;
|
|
275
|
+
ellipsoidHeight?: Meters; // required iff useGlobalHeight=false; WGS84
|
|
276
|
+
height?: Meters; // required iff useGlobalHeight=false
|
|
277
|
+
|
|
278
|
+
useGlobalSpeed: boolean;
|
|
279
|
+
waypointSpeed?: MetersPerSecond; // required iff useGlobalSpeed=false; [1, 15]
|
|
280
|
+
|
|
281
|
+
useGlobalHeadingParam: boolean;
|
|
282
|
+
waypointHeadingParam?: WaypointHeadingParam;
|
|
283
|
+
|
|
284
|
+
useGlobalTurnParam: boolean;
|
|
285
|
+
waypointTurnParam?: WaypointTurnParam;
|
|
286
|
+
|
|
287
|
+
useStraightLine?: boolean; // required for certain turn modes
|
|
288
|
+
gimbalPitchAngle?: Degrees; // required iff folder gimbalPitchMode === "usePointSetting"
|
|
289
|
+
isRisky?: boolean; // M30/M30T, M3D/M3TD, M4D/M4TD, M4E/M4T
|
|
290
|
+
|
|
291
|
+
actionGroup?: ActionGroup[];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
interface TemplatePolygonPlacemark {
|
|
295
|
+
Polygon: {
|
|
296
|
+
outerBoundaryIs: {
|
|
297
|
+
LinearRing: { coordinates: string }; // space-separated lng,lat,alt triples
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface TemplateLineStringPlacemark {
|
|
303
|
+
LineString: { coordinates: string }; // space-separated lng,lat,alt triples
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Waylines `Placemark`
|
|
308
|
+
|
|
309
|
+
The `useGlobalXxx` switches are template-only — in `waylines.wpml` all params are inlined unconditionally.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
interface WaylinesPlacemark {
|
|
313
|
+
Point: { coordinates: LngLat };
|
|
314
|
+
index: number;
|
|
315
|
+
executeHeight: Meters; // interpreted per Folder.executeHeightMode
|
|
316
|
+
waypointSpeed: MetersPerSecond; // [1, 15]
|
|
317
|
+
waypointHeadingParam: WaypointHeadingParam;
|
|
318
|
+
waypointTurnParam: WaypointTurnParam;
|
|
319
|
+
useStraightLine?: boolean;
|
|
320
|
+
isRisky?: boolean;
|
|
321
|
+
actionGroup?: ActionGroup[];
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### `WaypointHeadingParam` / `WaypointTurnParam`
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
interface WaypointHeadingParam {
|
|
329
|
+
waypointHeadingMode: "followWayline" | "manually" | "fixed" | "smoothTransition" | "towardPOI";
|
|
330
|
+
waypointHeadingAngle?: Degrees; // [-180, 180]; required iff mode === "smoothTransition"
|
|
331
|
+
waypointPoiPoint?: LngLatHeight; // required iff mode === "towardPOI"; height should be 0
|
|
332
|
+
waypointHeadingPathMode: "clockwise" | "counterClockwise" | "followBadArc"; // spec typo preserved
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
interface WaypointTurnParam {
|
|
336
|
+
waypointTurnMode:
|
|
337
|
+
| "coordinateTurn"
|
|
338
|
+
| "toPointAndStopWithDiscontinuityCurvature"
|
|
339
|
+
| "toPointAndStopWithContinuityCurvature"
|
|
340
|
+
| "toPointAndPassWithContinuityCurvature";
|
|
341
|
+
waypointTurnDampingDist?: Meters; // required for coordinateTurn or pass+useStraightLine
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
interface WaylineCoordinateSysParam {
|
|
345
|
+
coordinateMode: "WGS84"; // only value currently
|
|
346
|
+
heightMode: "EGM96" | "relativeToStartPoint" | "aboveGroundLevel" | "realTimeFollowSurface";
|
|
347
|
+
positioningType?: "GPS" | "RTKBaseStation" | "QianXun" | "Custom";
|
|
348
|
+
globalShootHeight?: Meters; // mapping templates
|
|
349
|
+
surfaceFollowModeEnable?: boolean; // mapping templates
|
|
350
|
+
surfaceRelativeHeight?: Meters; // required iff surfaceFollowModeEnable === true
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
interface MappingHeadingParam {
|
|
354
|
+
mappingHeadingMode: "fixed" | "followWayline";
|
|
355
|
+
mappingHeadingAngle?: Degrees; // [0, 360]; required iff mode === "fixed"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
interface Overlap {
|
|
359
|
+
orthoLidarOverlapH?: number; // 0–100, M300/M350 LiDAR
|
|
360
|
+
orthoLidarOverlapW?: number;
|
|
361
|
+
orthoCameraOverlapH?: number; // 0–100, visible light
|
|
362
|
+
orthoCameraOverlapW?: number;
|
|
363
|
+
inclinedLidarOverlapH?: number;
|
|
364
|
+
inclinedLidarOverlapW?: number;
|
|
365
|
+
inclinedCameraOverlapH?: number;
|
|
366
|
+
inclinedCameraOverlapW?: number;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### ActionGroup, Action, ActionTrigger
|
|
371
|
+
|
|
372
|
+
`actionGroupId` is unique across the **entire** KMZ (monotonically from 0). `actionId` is scoped within its `ActionGroup`.
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
interface ActionGroup {
|
|
376
|
+
actionGroupId: number;
|
|
377
|
+
actionGroupStartIndex: number;
|
|
378
|
+
actionGroupEndIndex: number; // ≥ start; equal means single-waypoint group
|
|
379
|
+
actionGroupMode: "sequence"; // only value currently
|
|
380
|
+
actionTrigger: ActionTrigger;
|
|
381
|
+
action: Action[]; // 1..*
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
interface ActionTrigger {
|
|
385
|
+
actionTriggerType: "reachPoint" | "betweenAdjacentPoints" | "multipleTiming" | "multipleDistance";
|
|
386
|
+
actionTriggerParam?: number; // seconds for multipleTiming; meters for multipleDistance
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
type Action =
|
|
390
|
+
| TakePhotoAction
|
|
391
|
+
| StartRecordAction
|
|
392
|
+
| StopRecordAction
|
|
393
|
+
| FocusAction
|
|
394
|
+
| ZoomAction
|
|
395
|
+
| CustomDirNameAction
|
|
396
|
+
| GimbalRotateAction
|
|
397
|
+
| RotateYawAction
|
|
398
|
+
| HoverAction
|
|
399
|
+
| GimbalEvenlyRotateAction
|
|
400
|
+
| AccurateShootAction
|
|
401
|
+
| OrientedShootAction
|
|
402
|
+
| PanoShotAction
|
|
403
|
+
| RecordPointCloudAction
|
|
404
|
+
| MegaphoneAction
|
|
405
|
+
| SearchlightAction;
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Every `Action` has the shape:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
interface ActionBase<F extends string, P> {
|
|
412
|
+
actionId: number;
|
|
413
|
+
actionActuatorFunc: F;
|
|
414
|
+
actionActuatorFuncParam: P; // shape depends on F
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
The 16 param shapes match [`docs/kmz/common-element.html` §action](../../docs/kmz/common-element.html) — every field, every model gating, every spec typo. Example:
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
interface TakePhotoParam {
|
|
422
|
+
payloadPositionIndex: number;
|
|
423
|
+
fileSuffix?: string;
|
|
424
|
+
payloadLensIndex?: LensType[]; // ["wide", "ir", ...]
|
|
425
|
+
useGlobalPayloadLensIndex: boolean;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
interface GimbalRotateParam {
|
|
429
|
+
payloadPositionIndex: number;
|
|
430
|
+
gimbalHeadingYawBase: "north";
|
|
431
|
+
gimbalRotateMode: "absoluteAngle";
|
|
432
|
+
gimbalPitchRotateEnable: boolean;
|
|
433
|
+
gimbalPitchRotateAngle: Degrees;
|
|
434
|
+
gimbalRollRotateEnable: boolean;
|
|
435
|
+
gimbalRollRotateAngle: Degrees;
|
|
436
|
+
gimbalYawRotateEnable: boolean;
|
|
437
|
+
gimbalYawRotateAngle: Degrees;
|
|
438
|
+
gimbalRotateTimeEnable: boolean;
|
|
439
|
+
gimbalRotateTime: number; // seconds
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
interface HoverParam {
|
|
443
|
+
hoverTime: number; // seconds, > 0
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
For the full list — including `accurateShoot` (deprecated, prefer `orientedShoot`), `megaphone`, `searchlight`, `panoShot`, `recordPointCloud`, etc. — see [`src/types/action.ts`](src/types/action.ts) or [`docs/kmz-types-spec.md` §9](../../docs/kmz-types-spec.md).
|
|
448
|
+
|
|
449
|
+
### Branded primitives
|
|
450
|
+
|
|
451
|
+
Nominal types to prevent unit mix-ups. They are plain numbers at runtime.
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
type Meters = number & { readonly __brand: "Meters" };
|
|
455
|
+
type MetersPerSecond = number & { readonly __brand: "MetersPerSecond" };
|
|
456
|
+
type Degrees = number & { readonly __brand: "Degrees" };
|
|
457
|
+
|
|
458
|
+
// Constructors (no-op at runtime)
|
|
459
|
+
const meters = (n: number): Meters => n as Meters;
|
|
460
|
+
const metersPerSecond = (n: number): MetersPerSecond => n as MetersPerSecond;
|
|
461
|
+
const degrees = (n: number): Degrees => n as Degrees;
|
|
462
|
+
|
|
463
|
+
// Geographic types
|
|
464
|
+
interface LngLat { lng: number; lat: number; }
|
|
465
|
+
interface LngLatHeight { lng: number; lat: number; height: Meters; }
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Enums
|
|
469
|
+
|
|
470
|
+
Every enum is a string-literal union — the literal value is the exact string the KMZ XML expects.
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
type FlyToWaylineMode = "safely" | "pointToPoint";
|
|
474
|
+
type FinishAction = "goHome" | "noAction" | "autoLand" | "gotoFirstWaypoint";
|
|
475
|
+
type ExitOnRCLost = "goContinue" | "executeLostAction";
|
|
476
|
+
type ExecuteRCLostAction = "goBack" | "landing" | "hover";
|
|
477
|
+
type TemplateType = "waypoint" | "mapping2d" | "mapping3d" | "mappingStrip";
|
|
478
|
+
type CoordinateMode = "WGS84";
|
|
479
|
+
type TemplateHeightMode = "EGM96" | "relativeToStartPoint" | "aboveGroundLevel" | "realTimeFollowSurface";
|
|
480
|
+
type ExecuteHeightMode = "WGS84" | "relativeToStartPoint" | "realTimeFollowSurface";
|
|
481
|
+
type WaypointTurnMode = "coordinateTurn" | "toPointAndStopWithDiscontinuityCurvature" | "toPointAndStopWithContinuityCurvature" | "toPointAndPassWithContinuityCurvature";
|
|
482
|
+
type WaypointHeadingMode = "followWayline" | "manually" | "fixed" | "smoothTransition" | "towardPOI";
|
|
483
|
+
type WaypointHeadingPathMode = "clockwise" | "counterClockwise" | "followBadArc"; // sic
|
|
484
|
+
type GimbalPitchMode = "manual" | "usePointSetting" | "fixed";
|
|
485
|
+
type ShootType = "time" | "distance";
|
|
486
|
+
type MappingHeadingMode = "fixed" | "followWayline";
|
|
487
|
+
type LensType = "wide" | "zoom" | "ir" | "narrow_band" | "visable"; // sic
|
|
488
|
+
type ActionTriggerType = "reachPoint" | "betweenAdjacentPoints" | "multipleTiming" | "multipleDistance";
|
|
489
|
+
type ActionActuatorFunc = /* 16 string literals — see Action union */;
|
|
490
|
+
type RecordPointCloudOperate = "startRecord" | "pauseRecord" | "resumeRecord" | "stopRecord";
|
|
491
|
+
type OrientedPhotoMode = "normalPhoto" | "lowLightSmartShooting";
|
|
492
|
+
type PositioningType = "GPS" | "RTKBaseStation" | "QianXun" | "Custom";
|
|
493
|
+
type FocusMode = "firstPoint" | "custom";
|
|
494
|
+
type MeteringMode = "average" | "spot";
|
|
495
|
+
type LidarReturnMode = "singleReturnStrongest" | "dualReturn" | "tripleReturn";
|
|
496
|
+
type LidarScanningMode = "repetitive" | "nonRepetitive";
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
> ⚠️ Spec typos preserved verbatim: `visable` (intended `visible`), `followBadArc` (intended "shorter arc"). Do not autocorrect when serializing.
|
|
500
|
+
|
|
501
|
+
### Drone & payload constants
|
|
502
|
+
|
|
503
|
+
Lookup tables for the `droneEnumValue`/`droneSubEnumValue` and `payloadEnumValue`/`payloadPositionIndex` integer pairs. Spread them straight into a `MissionConfig`.
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
import { DroneEnum, PayloadEnum } from "@lumikmz/kmz";
|
|
507
|
+
|
|
508
|
+
missionConfig: {
|
|
509
|
+
// ...
|
|
510
|
+
droneInfo: { ...DroneEnum.M3TD }, // → { droneEnumValue: 91, droneSubEnumValue: 1 }
|
|
511
|
+
payloadInfo: [{ ...PayloadEnum.M3TDCamera }], // → { payloadEnumValue: 81, payloadPositionIndex: 0 }
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`DroneEnum` keys: `M400`, `M350RTK`, `M300RTK`, `M30`, `M30T`, `M3E`, `M3T`, `M3TA`, `M3D`, `M3TD`, `M4D`, `M4TD`, `M4E`, `M4T`.
|
|
516
|
+
|
|
517
|
+
`PayloadEnum` keys: per-model main cameras (`M3TDCamera`, `M30TCamera`, …), Zenmuse mounts (`ZenmuseH30T`, `ZenmuseH20T`, …), plus `FPVMatriceSeries`, `AuxiliaryM3xM4x`, `DockCamera`.
|
|
518
|
+
|
|
519
|
+
For the full table see [`docs/kmz-types-spec.md` §11](../../docs/kmz-types-spec.md) — values come straight from [`docs/kmz/product-support.html`](../../docs/kmz/product-support.html).
|
|
520
|
+
|
|
521
|
+
## How `plan()` dispatches
|
|
522
|
+
|
|
523
|
+
The discriminator is **`template.Folder.templateType`** (nested one level under the root). Each variant of the `Template` union narrows to a concrete `Folder` type via the literal `templateType` field, so passing a `Template` to `plan()` is type-safe and exhaustive — adding a new template type would force a new switch arm.
|
|
524
|
+
|
|
525
|
+
Internally, type guards at `src/plan/index.ts` route to one of five algorithms:
|
|
526
|
+
|
|
527
|
+
- `planWaypoint` — Placemark-to-Placemark, resolving `useGlobalXxx` flags
|
|
528
|
+
- `planMapping2d` — boustrophedon scan inside the Polygon
|
|
529
|
+
- `planMapping3d` — five-Folder ortho+oblique pattern
|
|
530
|
+
- `planMappingStrip` — LineString-to-LineString with optional per-vertex altitude
|
|
531
|
+
|
|
532
|
+
Output is always a single `Waylines` object containing `1..n` `Folder` entries.
|
|
533
|
+
|
|
534
|
+
## Compatibility
|
|
535
|
+
|
|
536
|
+
[DJI WPML 1.0.2](https://developer.dji.com/doc/cloud-api-tutorial/en/feature-set/dji-wpml/template-kml.html) — see [`docs/kmz-types-spec.md`](../../docs/kmz-types-spec.md) for the distilled spec this package mirrors.
|
|
537
|
+
|
|
538
|
+
## License
|
|
539
|
+
|
|
540
|
+
[MIT](../../LICENSE)
|