@lumikmz/kmz-file 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 +258 -0
- package/dist/index.d.mts +48 -0
- package/dist/index.mjs +1140 -0
- package/dist/wasm_kmz.d.ts +80 -0
- package/dist/wasm_kmz.js +432 -0
- package/dist/wasm_kmz_bg.wasm +0 -0
- package/dist/wasm_kmz_bg.wasm.d.ts +12 -0
- package/package.json +36 -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,258 @@
|
|
|
1
|
+
# @lumikmz/kmz-file
|
|
2
|
+
|
|
3
|
+
Read and write DJI `.kmz` mission files in JavaScript. Wraps a Rust + WebAssembly core that handles ZIP packaging and XML serialization, surfacing only the typed `Template` / `Waylines` / resources shapes defined in [`@lumikmz/kmz`](../kmz/README.md).
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @lumikmz/kmz @lumikmz/kmz-file
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
- [What this package does](#what-this-package-does)
|
|
12
|
+
- [Public API](#public-api)
|
|
13
|
+
- [`init(input?)`](#initinput--promiseinitoutput)
|
|
14
|
+
- [`initSync(input)`](#initsyncinput--initoutput)
|
|
15
|
+
- [`pack(template, waylines, resources?)`](#packtemplate-waylines-resources--uint8array)
|
|
16
|
+
- [`unpack(bytes)`](#unpackbytes--unpackresult)
|
|
17
|
+
- [Types](#types)
|
|
18
|
+
- [Resource files](#resource-files)
|
|
19
|
+
- [Bundler integration](#bundler-integration)
|
|
20
|
+
- [Round-trip guarantees](#round-trip-guarantees)
|
|
21
|
+
- [How it works](#how-it-works)
|
|
22
|
+
|
|
23
|
+
## What this package does
|
|
24
|
+
|
|
25
|
+
KMZ is a ZIP archive containing `wpmz/template.kml` (XML), `wpmz/waylines.wpml` (XML), and optional resource files (`res/audio/*.opus`, etc.). This package handles the full conversion:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
.kmz bytes ⇄ { template: Template, waylines: Waylines, resources: ResourceFile[] }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The Rust core (compiled to WASM) does ZIP + XML I/O. A TypeScript codec layer encodes/decodes between the on-wire XML JSON shape and the spec-aligned typed shape from `@lumikmz/kmz`. Consumers only see the typed shape.
|
|
32
|
+
|
|
33
|
+
## Public API
|
|
34
|
+
|
|
35
|
+
### `init(input?) → Promise<InitOutput>`
|
|
36
|
+
|
|
37
|
+
Lazily initialize the WASM module. Idempotent — subsequent calls return the same in-flight promise. Must complete before calling `pack` / `unpack`.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { init } from "@lumikmz/kmz-file";
|
|
41
|
+
|
|
42
|
+
await init(); // default: fetches the wasm binary alongside the JS bundle
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can override the loader by passing an `InitInput` (URL, `Response`, `BufferSource`, or `WebAssembly.Module`), useful when your bundler needs explicit asset wiring:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { init } from "@lumikmz/kmz-file";
|
|
49
|
+
// Bundler-friendly explicit URL:
|
|
50
|
+
import wasmUrl from "@lumikmz/kmz-file/wasm?url";
|
|
51
|
+
|
|
52
|
+
await init(wasmUrl);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `initSync(input) → InitOutput`
|
|
56
|
+
|
|
57
|
+
Synchronous variant for when you already have the WASM bytes or a compiled `WebAssembly.Module`. Same idempotency rules don't apply — call this exactly once.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { initSync } from "@lumikmz/kmz-file";
|
|
61
|
+
|
|
62
|
+
const bytes = await (await fetch("/wasm_kmz_bg.wasm")).arrayBuffer();
|
|
63
|
+
initSync({ module: bytes });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `pack(template, waylines, resources?) → Uint8Array`
|
|
67
|
+
|
|
68
|
+
Encode a `Template` + `Waylines` (plus optional resource files) into a `.kmz` byte stream. Pure synchronous after `init()`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { plan } from "@lumikmz/kmz";
|
|
72
|
+
import { init, pack, type ResourceFile } from "@lumikmz/kmz-file";
|
|
73
|
+
|
|
74
|
+
await init();
|
|
75
|
+
|
|
76
|
+
const waylines = plan(template);
|
|
77
|
+
const bytes: Uint8Array = pack(template, waylines);
|
|
78
|
+
|
|
79
|
+
// With resources (e.g. a megaphone audio file referenced by an action):
|
|
80
|
+
const resources: ResourceFile[] = [
|
|
81
|
+
{ path: "wpmz/res/audio/announcement.opus", data: audioBytes },
|
|
82
|
+
];
|
|
83
|
+
const bytes2 = pack(template, waylines, resources);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Signature:**
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
function pack(
|
|
90
|
+
template: Template,
|
|
91
|
+
waylines: Waylines,
|
|
92
|
+
resources?: ResourceFile[],
|
|
93
|
+
): Uint8Array;
|
|
94
|
+
|
|
95
|
+
interface ResourceFile {
|
|
96
|
+
path: string; // path inside the KMZ archive, e.g. "wpmz/res/audio/<hash>.opus"
|
|
97
|
+
data: Uint8Array;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `unpack(bytes) → UnpackResult`
|
|
102
|
+
|
|
103
|
+
Decode a `.kmz` byte stream into the typed shape. Pure synchronous after `init()`.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { unpack } from "@lumikmz/kmz-file";
|
|
107
|
+
|
|
108
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
109
|
+
const { template, waylines, resources } = unpack(bytes);
|
|
110
|
+
// ^Template ^Waylines ^ResourceFile[]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Signature:**
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
function unpack(bytes: Uint8Array): UnpackResult;
|
|
117
|
+
|
|
118
|
+
interface UnpackResult {
|
|
119
|
+
template: Template;
|
|
120
|
+
waylines: Waylines;
|
|
121
|
+
resources: ResourceFile[];
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Types
|
|
126
|
+
|
|
127
|
+
All types are re-exported alongside the functions. The main shapes — `Template`, `Waylines`, `MissionConfig`, `ActionGroup`, etc. — come from [`@lumikmz/kmz`](../kmz/README.md). This package adds:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
interface ResourceFile {
|
|
131
|
+
path: string;
|
|
132
|
+
data: Uint8Array;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface UnpackResult {
|
|
136
|
+
template: Template;
|
|
137
|
+
waylines: Waylines;
|
|
138
|
+
resources: ResourceFile[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// WASM init types (passed through from wasm-bindgen):
|
|
142
|
+
type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
143
|
+
type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
144
|
+
interface InitOutput { /* the raw wasm-bindgen exports */ }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Resource files
|
|
148
|
+
|
|
149
|
+
`resources` is the array of non-XML files inside the KMZ archive. DJI uses these for:
|
|
150
|
+
|
|
151
|
+
- **`/wpmz/res/audio/*.opus`** — Megaphone audio clips referenced by `megaphone` actions via `actionActuatorFuncParam.megaphoneOperateFilePath`
|
|
152
|
+
- Anything else DJI ships inside the archive (currently rare)
|
|
153
|
+
|
|
154
|
+
`path` is the in-archive path verbatim — usually rooted at `wpmz/`. When packing, pass exactly the paths your actions reference; when unpacking, you get every non-XML file the archive contained.
|
|
155
|
+
|
|
156
|
+
## Bundler integration
|
|
157
|
+
|
|
158
|
+
The package ships:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
dist/index.mjs # bundled JS, ESM
|
|
162
|
+
dist/index.d.mts # type declarations
|
|
163
|
+
dist/wasm_kmz.js # wasm-bindgen glue (loaded at runtime)
|
|
164
|
+
dist/wasm_kmz.d.ts
|
|
165
|
+
dist/wasm_kmz_bg.wasm # the WASM binary (~250 KB, ~127 KB gzipped)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`dist/index.mjs` imports `./wasm_kmz.js` at runtime, which in turn `fetch()`-loads `./wasm_kmz_bg.wasm` from the same directory. Modern bundlers (Vite, Webpack 5, Rollup with `@web/rollup-plugin-import-meta-assets`, etc.) resolve this automatically and emit the `.wasm` as a hashed asset.
|
|
169
|
+
|
|
170
|
+
If your bundler doesn't handle wasm-bindgen's loader out of the box, pass an explicit URL to `init()`:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { init } from "@lumikmz/kmz-file";
|
|
174
|
+
|
|
175
|
+
await init(new URL("@lumikmz/kmz-file/wasm", import.meta.url));
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The `./wasm` export in `package.json` maps to `dist/wasm_kmz_bg.wasm`.
|
|
179
|
+
|
|
180
|
+
## Round-trip guarantees
|
|
181
|
+
|
|
182
|
+
`pack` then `unpack` is shape-preserving for all spec-compliant fields:
|
|
183
|
+
|
|
184
|
+
- All `wpml:` field names are mapped between camelCase (typed) and the XML-namespaced form.
|
|
185
|
+
- Booleans round-trip via `"0"` / `"1"` strings.
|
|
186
|
+
- Numbers, branded numbers, and unit strings round-trip via `toString()` / `parseFloat`.
|
|
187
|
+
- `LngLat` ↔ KML `"lng,lat"` strings (longitude first).
|
|
188
|
+
- `LngLatHeight` (for `takeOffRefPoint` / `waypointPoiPoint`) ↔ DJI `"lat,lng,height"` strings (**latitude first** — opposite order from KML, easy to swap by accident; the codec handles this).
|
|
189
|
+
- Polygon `LinearRing.coordinates` and `LineString.coordinates` are passed through as the space-separated `"lng,lat,alt"` triples DJI expects.
|
|
190
|
+
- Single-vs-array elements are normalized: a single `<Folder>` deserializes as one object, not a one-element array, and serializes back without becoming an array.
|
|
191
|
+
- Spec misspellings (`visable`, `followBadArc`) are preserved verbatim — both directions.
|
|
192
|
+
- Action-specific params live inside `actionActuatorFuncParam` and are demultiplexed by `actionActuatorFunc`.
|
|
193
|
+
|
|
194
|
+
If a field is absent in the source it's absent in the output. The codec never invents defaults.
|
|
195
|
+
|
|
196
|
+
## How it works
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
┌───────────────────────────────────────────────────┐
|
|
200
|
+
│ @lumikmz/kmz-file │
|
|
201
|
+
│ │
|
|
202
|
+
│ ┌──────────────┐ ┌────────────────────────┐ │
|
|
203
|
+
unpack │ │ TS codec │ ← │ Rust (WASM) │ │ ← .kmz bytes
|
|
204
|
+
│ │ decode-*.ts │ │ - ZIP unpack │ │
|
|
205
|
+
│ └──────┬───────┘ │ - XML → JSON (serde) │ │
|
|
206
|
+
│ │ └────────────────────────┘ │
|
|
207
|
+
│ ↓ │
|
|
208
|
+
│ { Template, Waylines, resources } │
|
|
209
|
+
│ ↑ │
|
|
210
|
+
│ │ ┌────────────────────────┐ │
|
|
211
|
+
pack │ ┌──────┴───────┐ │ Rust (WASM) │ │ → .kmz bytes
|
|
212
|
+
│ │ TS codec │ → │ - JSON → XML │ │
|
|
213
|
+
│ │ encode-*.ts │ │ - ZIP pack │ │
|
|
214
|
+
│ └──────────────┘ └────────────────────────┘ │
|
|
215
|
+
└───────────────────────────────────────────────────┘
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The Rust layer is intentionally dumb: it just shuffles bytes between ZIP entries and the `serde_json::Value` representation of the XML tree. All spec knowledge lives in TypeScript, where it can be evolved alongside the type definitions.
|
|
219
|
+
|
|
220
|
+
### Source layout
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
packages/kmz-file/
|
|
224
|
+
├── rust/ # Rust crate compiled to WASM via wasm-bindgen
|
|
225
|
+
│ ├── src/lib.rs # wasm_bindgen entry points: kmzToJson, jsonToKmz, xmlToJson, jsonToXml
|
|
226
|
+
│ ├── src/kmz/ # ZIP read/write
|
|
227
|
+
│ └── src/xml/ # XML ↔ serde_json::Value
|
|
228
|
+
├── src/ # TypeScript layer
|
|
229
|
+
│ ├── codec/ # XML JSON ↔ typed JSON (internal, not exported)
|
|
230
|
+
│ │ ├── encode-*.ts
|
|
231
|
+
│ │ ├── decode-*.ts
|
|
232
|
+
│ │ └── xml-schema.ts
|
|
233
|
+
│ ├── pack.ts # public
|
|
234
|
+
│ ├── unpack.ts # public
|
|
235
|
+
│ ├── init.ts # public
|
|
236
|
+
│ └── index.ts # entry point
|
|
237
|
+
├── scripts/build.js # cargo build + wasm-bindgen + wasm-opt + vp pack
|
|
238
|
+
└── dist/ # publishable artifacts
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Build
|
|
242
|
+
|
|
243
|
+
Rebuilding requires the Rust toolchain (only when the WASM core changes):
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
cd packages/kmz-file
|
|
247
|
+
pnpm build # invokes scripts/build.js
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Editing only the TypeScript codec (the common case) needs nothing beyond `vp pack`.
|
|
251
|
+
|
|
252
|
+
## Compatibility
|
|
253
|
+
|
|
254
|
+
[DJI WPML 1.0.2](https://developer.dji.com/doc/cloud-api-tutorial/en/feature-set/dji-wpml/template-kml.html). The XML namespaces and structure are pinned in [`src/codec/xml-schema.ts`](src/codec/xml-schema.ts).
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
[MIT](../../LICENSE)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { InitInput, InitOutput, SyncInitInput } from "./wasm_kmz.js";
|
|
2
|
+
import { Template, Waylines } from "@lumikmz/kmz";
|
|
3
|
+
|
|
4
|
+
//#region src/init.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the underlying WASM module. Idempotent — repeated calls return the
|
|
7
|
+
* original promise. Pass `input` to control how the module is loaded (e.g. a
|
|
8
|
+
* URL, fetch Response, or pre-fetched bytes). With no arguments,
|
|
9
|
+
* wasm-bindgen uses its default loader.
|
|
10
|
+
*/
|
|
11
|
+
declare function init(input?: InitInput | Promise<InitInput>): Promise<InitOutput>;
|
|
12
|
+
/** Synchronous WASM init. Accepts pre-fetched bytes or a compiled module. */
|
|
13
|
+
declare function initSync(input: {
|
|
14
|
+
module: SyncInitInput;
|
|
15
|
+
} | SyncInitInput): InitOutput;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/pack.d.ts
|
|
18
|
+
interface ResourceFile {
|
|
19
|
+
path: string;
|
|
20
|
+
data: Uint8Array;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encode a Template + Waylines pair (plus optional resources) into a KMZ byte
|
|
24
|
+
* stream. `init()` must have completed before calling this.
|
|
25
|
+
*/
|
|
26
|
+
declare function pack(template: Template, waylines: Waylines, resources?: ResourceFile[]): Uint8Array;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/unpack.d.ts
|
|
29
|
+
interface UnpackResult {
|
|
30
|
+
template: Template;
|
|
31
|
+
waylines: Waylines;
|
|
32
|
+
resources: ResourceFile[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Decode a KMZ byte stream into typed Template + Waylines. `init()` must have
|
|
36
|
+
* completed before calling this.
|
|
37
|
+
*/
|
|
38
|
+
declare function unpack(bytes: Uint8Array): UnpackResult;
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/index.d.ts
|
|
41
|
+
declare const _default: {
|
|
42
|
+
pack: typeof pack;
|
|
43
|
+
unpack: typeof unpack;
|
|
44
|
+
init: typeof init;
|
|
45
|
+
initSync: typeof initSync;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { type InitInput, type InitOutput, type ResourceFile, type SyncInitInput, type UnpackResult, _default as default, init, initSync, pack, unpack };
|