@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 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)