@packet-net/pdn-aprs 0.0.1

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +661 -0
  3. package/README.md +192 -0
  4. package/THIRD-PARTY-NOTICES.md +5 -0
  5. package/dist/builder.d.ts +232 -0
  6. package/dist/builder.d.ts.map +1 -0
  7. package/dist/builder.js +508 -0
  8. package/dist/builder.js.map +1 -0
  9. package/dist/bytes.d.ts +19 -0
  10. package/dist/bytes.d.ts.map +1 -0
  11. package/dist/bytes.js +120 -0
  12. package/dist/bytes.js.map +1 -0
  13. package/dist/codes.d.ts +13 -0
  14. package/dist/codes.d.ts.map +1 -0
  15. package/dist/codes.js +112 -0
  16. package/dist/codes.js.map +1 -0
  17. package/dist/decode/context.d.ts +33 -0
  18. package/dist/decode/context.d.ts.map +1 -0
  19. package/dist/decode/context.js +65 -0
  20. package/dist/decode/context.js.map +1 -0
  21. package/dist/decode/index.d.ts +10 -0
  22. package/dist/decode/index.d.ts.map +1 -0
  23. package/dist/decode/index.js +121 -0
  24. package/dist/decode/index.js.map +1 -0
  25. package/dist/decode/message.d.ts +5 -0
  26. package/dist/decode/message.d.ts.map +1 -0
  27. package/dist/decode/message.js +190 -0
  28. package/dist/decode/message.js.map +1 -0
  29. package/dist/decode/other.d.ts +25 -0
  30. package/dist/decode/other.d.ts.map +1 -0
  31. package/dist/decode/other.js +326 -0
  32. package/dist/decode/other.js.map +1 -0
  33. package/dist/decode/positioned.d.ts +132 -0
  34. package/dist/decode/positioned.d.ts.map +1 -0
  35. package/dist/decode/positioned.js +1063 -0
  36. package/dist/decode/positioned.js.map +1 -0
  37. package/dist/decode/reports.d.ts +13 -0
  38. package/dist/decode/reports.d.ts.map +1 -0
  39. package/dist/decode/reports.js +375 -0
  40. package/dist/decode/reports.js.map +1 -0
  41. package/dist/decoder.d.ts +23 -0
  42. package/dist/decoder.d.ts.map +1 -0
  43. package/dist/decoder.js +75 -0
  44. package/dist/decoder.js.map +1 -0
  45. package/dist/deviceid-data.d.ts +33 -0
  46. package/dist/deviceid-data.d.ts.map +1 -0
  47. package/dist/deviceid-data.js +2941 -0
  48. package/dist/deviceid-data.js.map +1 -0
  49. package/dist/deviceid.d.ts +12 -0
  50. package/dist/deviceid.d.ts.map +1 -0
  51. package/dist/deviceid.js +86 -0
  52. package/dist/deviceid.js.map +1 -0
  53. package/dist/diagnostics.d.ts +48 -0
  54. package/dist/diagnostics.d.ts.map +1 -0
  55. package/dist/diagnostics.js +65 -0
  56. package/dist/diagnostics.js.map +1 -0
  57. package/dist/encode/common.d.ts +19 -0
  58. package/dist/encode/common.d.ts.map +1 -0
  59. package/dist/encode/common.js +84 -0
  60. package/dist/encode/common.js.map +1 -0
  61. package/dist/encode/index.d.ts +35 -0
  62. package/dist/encode/index.d.ts.map +1 -0
  63. package/dist/encode/index.js +248 -0
  64. package/dist/encode/index.js.map +1 -0
  65. package/dist/encode/other.d.ts +21 -0
  66. package/dist/encode/other.d.ts.map +1 -0
  67. package/dist/encode/other.js +253 -0
  68. package/dist/encode/other.js.map +1 -0
  69. package/dist/encode/position.d.ts +31 -0
  70. package/dist/encode/position.d.ts.map +1 -0
  71. package/dist/encode/position.js +729 -0
  72. package/dist/encode/position.js.map +1 -0
  73. package/dist/header.d.ts +42 -0
  74. package/dist/header.d.ts.map +1 -0
  75. package/dist/header.js +206 -0
  76. package/dist/header.js.map +1 -0
  77. package/dist/index.d.ts +13 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +14 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/neutral.d.ts +22 -0
  82. package/dist/neutral.d.ts.map +1 -0
  83. package/dist/neutral.js +593 -0
  84. package/dist/neutral.js.map +1 -0
  85. package/dist/symbols-data.d.ts +832 -0
  86. package/dist/symbols-data.d.ts.map +1 -0
  87. package/dist/symbols-data.js +173 -0
  88. package/dist/symbols-data.js.map +1 -0
  89. package/dist/symbols.d.ts +35 -0
  90. package/dist/symbols.d.ts.map +1 -0
  91. package/dist/symbols.js +71 -0
  92. package/dist/symbols.js.map +1 -0
  93. package/dist/types.d.ts +458 -0
  94. package/dist/types.d.ts.map +1 -0
  95. package/dist/types.js +4 -0
  96. package/dist/types.js.map +1 -0
  97. package/package.json +68 -0
  98. package/src/builder.ts +617 -0
  99. package/src/bytes.ts +118 -0
  100. package/src/codes.ts +218 -0
  101. package/src/decode/context.ts +63 -0
  102. package/src/decode/index.ts +135 -0
  103. package/src/decode/message.ts +194 -0
  104. package/src/decode/other.ts +307 -0
  105. package/src/decode/positioned.ts +1161 -0
  106. package/src/decode/reports.ts +378 -0
  107. package/src/decoder.ts +76 -0
  108. package/src/deviceid-data.ts +2954 -0
  109. package/src/deviceid.ts +83 -0
  110. package/src/diagnostics.ts +86 -0
  111. package/src/encode/common.ts +87 -0
  112. package/src/encode/index.ts +280 -0
  113. package/src/encode/other.ts +247 -0
  114. package/src/encode/position.ts +656 -0
  115. package/src/header.ts +221 -0
  116. package/src/index.ts +57 -0
  117. package/src/neutral.ts +595 -0
  118. package/src/symbols-data.ts +181 -0
  119. package/src/symbols.ts +85 -0
  120. package/src/types.ts +571 -0
package/src/neutral.ts ADDED
@@ -0,0 +1,595 @@
1
+ // The conformance vectors' language-neutral form (https://github.com/packet-net/aprs-vectors):
2
+ // snake_case names, empty values left out, booleans only when true, timestamps and symbols as
3
+ // sent. Used by the conformance tests and the differential dump, and handy for comparing this
4
+ // library with another implementation.
5
+
6
+ import { formatDiagnostic } from './diagnostics.js';
7
+ import type {
8
+ AprsData,
9
+ AprsPacket,
10
+ AprsSymbol,
11
+ AprsTimestamp,
12
+ AreaObject,
13
+ CommentTelemetry,
14
+ CompressionType,
15
+ Dao,
16
+ DfBearing,
17
+ Dfs,
18
+ PathEntry,
19
+ Phg,
20
+ PositionedFields,
21
+ Storm,
22
+ VoiceFrequency,
23
+ Weather,
24
+ } from './types.js';
25
+
26
+ /** A JSON object. */
27
+ export type Neutral = { [key: string]: unknown };
28
+
29
+ function put(o: Neutral, key: string, value: unknown): void {
30
+ if (value === undefined || value === null || value === false || value === '') return;
31
+ if (Array.isArray(value) && value.length === 0) return;
32
+ o[key] = value;
33
+ }
34
+
35
+ const pad2 = (n: number): string => String(n).padStart(2, '0');
36
+
37
+ /** A timestamp as sent: `092345z`, `092345/`, `234517h` or `10090556`. */
38
+ export function formatTimestamp(t: AprsTimestamp): string {
39
+ switch (t.kind) {
40
+ case 'dhm-zulu':
41
+ return `${pad2(t.day)}${pad2(t.hour)}${pad2(t.minute)}z`;
42
+ case 'dhm-local':
43
+ return `${pad2(t.day)}${pad2(t.hour)}${pad2(t.minute)}/`;
44
+ case 'hms':
45
+ return `${pad2(t.hour)}${pad2(t.minute)}${pad2(t.second)}h`;
46
+ case 'mdhm':
47
+ return `${pad2(t.month)}${pad2(t.day)}${pad2(t.hour)}${pad2(t.minute)}`;
48
+ }
49
+ }
50
+
51
+ /** Parses a timestamp as sent; `undefined` if it is not one. */
52
+ export function parseTimestamp(text: string): AprsTimestamp | undefined {
53
+ let m = /^([0-9]{2})([0-9]{2})([0-9]{2})([z/h])$/.exec(text);
54
+ if (m) {
55
+ const [a, b, c] = [Number(m[1]), Number(m[2]), Number(m[3])];
56
+ if (m[4] === 'z') return { kind: 'dhm-zulu', day: a, hour: b, minute: c };
57
+ if (m[4] === '/') return { kind: 'dhm-local', day: a, hour: b, minute: c };
58
+ return { kind: 'hms', hour: a, minute: b, second: c };
59
+ }
60
+ m = /^([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$/.exec(text);
61
+ if (m) return { kind: 'mdhm', month: Number(m[1]), day: Number(m[2]), hour: Number(m[3]), minute: Number(m[4]) };
62
+ return undefined;
63
+ }
64
+
65
+ const sym = (s: AprsSymbol): string => s.table + s.code;
66
+
67
+ function compressionN(c: CompressionType): Neutral {
68
+ return { fix: c.fix, source: c.source, origin: c.origin };
69
+ }
70
+
71
+ function phgN(p: Phg): Neutral {
72
+ const o: Neutral = { power: p.power, height: p.height, gain: p.gain, directivity: p.directivity };
73
+ put(o, 'beacons_per_hour', p.beaconsPerHour);
74
+ return o;
75
+ }
76
+
77
+ function dfsN(d: Dfs): Neutral {
78
+ return { strength: d.strength, height: d.height, gain: d.gain, directivity: d.directivity };
79
+ }
80
+
81
+ function areaN(a: AreaObject): Neutral {
82
+ const o: Neutral = { shape: a.shape, color: a.color, lat_offset: a.latOffset, lon_offset: a.lonOffset };
83
+ put(o, 'corridor_width_miles', a.corridorWidthMiles);
84
+ return o;
85
+ }
86
+
87
+ function dfBearingN(d: DfBearing): Neutral {
88
+ return { bearing_degrees: d.bearingDegrees, number: d.number, range: d.range, quality: d.quality };
89
+ }
90
+
91
+ function stormN(s: Storm): Neutral {
92
+ const o: Neutral = { type: s.type };
93
+ put(o, 'sustained_wind_knots', s.sustainedWindKnots);
94
+ put(o, 'gust_knots', s.gustKnots);
95
+ put(o, 'central_pressure_mbar', s.centralPressureMbar);
96
+ put(o, 'hurricane_radius_nm', s.hurricaneRadiusNm);
97
+ put(o, 'tropical_storm_radius_nm', s.tropicalStormRadiusNm);
98
+ put(o, 'whole_gale_radius_nm', s.wholeGaleRadiusNm);
99
+ return o;
100
+ }
101
+
102
+ function daoN(d: Dao): Neutral {
103
+ return { datum: d.datum, precision: d.precision };
104
+ }
105
+
106
+ function telemetryN(t: CommentTelemetry): Neutral {
107
+ const o: Neutral = { sequence: t.sequence, analog: [...t.analog] };
108
+ put(o, 'digital', t.digital);
109
+ return o;
110
+ }
111
+
112
+ function frequencyN(f: VoiceFrequency): Neutral {
113
+ const o: Neutral = { mhz: f.mhz };
114
+ put(o, 'tone', f.tone);
115
+ put(o, 'tone_value', f.toneValue);
116
+ put(o, 'offset_khz', f.offsetKhz);
117
+ put(o, 'range', f.range);
118
+ put(o, 'range_km', f.rangeKm);
119
+ put(o, 'narrow', f.narrow);
120
+ put(o, 'ten_khz_resolution', f.tenKhzResolution);
121
+ return o;
122
+ }
123
+
124
+ function weatherN(w: Weather): Neutral {
125
+ const o: Neutral = {};
126
+ put(o, 'wind_direction_degrees', w.windDirectionDegrees);
127
+ put(o, 'wind_speed_mph', w.windSpeedMph);
128
+ put(o, 'wind_gust_mph', w.windGustMph);
129
+ put(o, 'temperature_f', w.temperatureF);
130
+ put(o, 'rain_1h_in', w.rain1hIn);
131
+ put(o, 'rain_24h_in', w.rain24hIn);
132
+ put(o, 'rain_midnight_in', w.rainMidnightIn);
133
+ put(o, 'rain_raw', w.rainRaw);
134
+ put(o, 'humidity_percent', w.humidityPercent);
135
+ put(o, 'pressure_mbar', w.pressureMbar);
136
+ put(o, 'luminosity_w_m2', w.luminosityWM2);
137
+ put(o, 'snow_24h_in', w.snow24hIn);
138
+ put(o, 'software', w.software);
139
+ put(o, 'unit', w.unit);
140
+ if (w.extra && w.extra.length > 0) o.extra = w.extra.map((e) => ({ letter: e.letter, value: e.value }));
141
+ return o;
142
+ }
143
+
144
+ function positionedN(o: Neutral, d: PositionedFields): void {
145
+ o.latitude = d.latitude;
146
+ o.longitude = d.longitude;
147
+ put(o, 'ambiguity', d.ambiguity);
148
+ o.symbol = sym(d.symbol);
149
+ put(o, 'compressed', d.compressed);
150
+ if (d.compression) o.compression = compressionN(d.compression);
151
+ put(o, 'course_degrees', d.courseDegrees);
152
+ put(o, 'speed_knots', d.speedKnots);
153
+ put(o, 'altitude_feet', d.altitudeFeet);
154
+ if (d.phg) o.phg = phgN(d.phg);
155
+ put(o, 'range_miles', d.rangeMiles);
156
+ if (d.dfs) o.dfs = dfsN(d.dfs);
157
+ if (d.area) o.area = areaN(d.area);
158
+ if (d.dfBearing) o.df_bearing = dfBearingN(d.dfBearing);
159
+ if (d.storm) o.storm = stormN(d.storm);
160
+ if (d.dao) o.dao = daoN(d.dao);
161
+ if (d.telemetry) o.telemetry = telemetryN(d.telemetry);
162
+ if (d.frequency) o.frequency = frequencyN(d.frequency);
163
+ if (d.weather) o.weather = weatherN(d.weather);
164
+ put(o, 'signpost', d.signpost);
165
+ put(o, 'comment', d.comment);
166
+ }
167
+
168
+ /** Formats a path entry as the neutral form writes it: `WIDE1-1`, or `N2GH*` when used. */
169
+ export function pathEntryN(e: PathEntry): string {
170
+ return e.used ? `${e.address}*` : e.address;
171
+ }
172
+
173
+ /** A packet's data in the neutral form. */
174
+ export function toNeutralData(d: AprsData): Neutral {
175
+ const o: Neutral = { type: d.type };
176
+ switch (d.type) {
177
+ case 'position':
178
+ if (d.timestamp) o.timestamp = formatTimestamp(d.timestamp);
179
+ put(o, 'messaging', d.messaging);
180
+ positionedN(o, d);
181
+ break;
182
+ case 'mic-e':
183
+ o.mic_e_message = d.micEMessage;
184
+ put(o, 'old_data', d.oldData);
185
+ put(o, 'type_code', d.typeCode);
186
+ put(o, 'device_suffix', d.deviceSuffix);
187
+ put(o, 'locator', d.locator);
188
+ if (d.legacyTelemetry && d.legacyTelemetry.length > 0) o.legacy_telemetry = [...d.legacyTelemetry];
189
+ put(o, 'destination_ssid', d.destinationSsid);
190
+ positionedN(o, d);
191
+ break;
192
+ case 'object':
193
+ o.name = d.name;
194
+ put(o, 'killed', d.killed);
195
+ if (d.timestamp) o.timestamp = formatTimestamp(d.timestamp);
196
+ positionedN(o, d);
197
+ break;
198
+ case 'item':
199
+ o.name = d.name;
200
+ put(o, 'killed', d.killed);
201
+ positionedN(o, d);
202
+ break;
203
+ case 'message':
204
+ put(o, 'addressee', d.addressee);
205
+ put(o, 'text', d.text);
206
+ put(o, 'message_id', d.messageId);
207
+ if (d.replyAck !== undefined) o.reply_ack = d.replyAck;
208
+ break;
209
+ case 'ack':
210
+ put(o, 'addressee', d.addressee);
211
+ put(o, 'acked_id', d.ackedId);
212
+ if (d.replyAck !== undefined) o.reply_ack = d.replyAck;
213
+ break;
214
+ case 'reject':
215
+ put(o, 'addressee', d.addressee);
216
+ put(o, 'rejected_id', d.rejectedId);
217
+ if (d.replyAck !== undefined) o.reply_ack = d.replyAck;
218
+ break;
219
+ case 'bulletin':
220
+ case 'nws-bulletin':
221
+ put(o, 'addressee', d.addressee);
222
+ put(o, 'text', d.text);
223
+ put(o, 'message_id', d.messageId);
224
+ break;
225
+ case 'telemetry-names':
226
+ put(o, 'addressee', d.addressee);
227
+ put(o, 'names', [...d.names]);
228
+ put(o, 'message_id', d.messageId);
229
+ break;
230
+ case 'telemetry-units':
231
+ put(o, 'addressee', d.addressee);
232
+ put(o, 'units', [...d.units]);
233
+ put(o, 'message_id', d.messageId);
234
+ break;
235
+ case 'telemetry-coefficients':
236
+ put(o, 'addressee', d.addressee);
237
+ put(o, 'coefficients', [...d.coefficients]);
238
+ put(o, 'message_id', d.messageId);
239
+ break;
240
+ case 'telemetry-bits':
241
+ put(o, 'addressee', d.addressee);
242
+ put(o, 'bits', d.bits);
243
+ put(o, 'project', d.project);
244
+ put(o, 'message_id', d.messageId);
245
+ break;
246
+ case 'directed-query':
247
+ put(o, 'addressee', d.addressee);
248
+ put(o, 'query_type', d.queryType);
249
+ put(o, 'target', d.target);
250
+ break;
251
+ case 'status':
252
+ if (d.timestamp) o.timestamp = formatTimestamp(d.timestamp);
253
+ put(o, 'locator', d.locator);
254
+ if (d.symbol) o.symbol = sym(d.symbol);
255
+ if (d.beam) o.beam = { heading_code: d.beam.headingCode, power_code: d.beam.powerCode };
256
+ put(o, 'text', d.text);
257
+ break;
258
+ case 'telemetry':
259
+ put(o, 'sequence', d.sequence);
260
+ if (d.analog.length > 0) o.analog = [...d.analog];
261
+ put(o, 'bits', d.bits);
262
+ put(o, 'comment', d.comment);
263
+ break;
264
+ case 'weather':
265
+ if (d.timestamp) o.timestamp = formatTimestamp(d.timestamp);
266
+ o.weather = weatherN(d.weather);
267
+ put(o, 'comment', d.comment);
268
+ break;
269
+ case 'raw-weather':
270
+ o.format = d.format;
271
+ put(o, 'data', d.data);
272
+ break;
273
+ case 'nmea':
274
+ put(o, 'sentence', d.sentence);
275
+ put(o, 'has_checksum', d.hasChecksum);
276
+ put(o, 'latitude', d.latitude);
277
+ put(o, 'longitude', d.longitude);
278
+ put(o, 'fix', d.fix);
279
+ put(o, 'course_degrees', d.courseDegrees);
280
+ put(o, 'speed_knots', d.speedKnots);
281
+ put(o, 'altitude_m', d.altitudeM);
282
+ put(o, 'time', d.time);
283
+ put(o, 'waypoint', d.waypoint);
284
+ break;
285
+ case 'maidenhead-beacon':
286
+ put(o, 'locator', d.locator);
287
+ put(o, 'comment', d.comment);
288
+ break;
289
+ case 'query':
290
+ put(o, 'query_type', d.queryType);
291
+ if (d.footprint)
292
+ o.footprint = {
293
+ latitude: d.footprint.latitude,
294
+ longitude: d.footprint.longitude,
295
+ radius_miles: d.footprint.radiusMiles,
296
+ };
297
+ break;
298
+ case 'capabilities':
299
+ if (d.capabilities.length > 0) o.capabilities = d.capabilities.map((c) => [...c]);
300
+ break;
301
+ case 'third-party':
302
+ o.packet = toNeutralInner(d.packet);
303
+ break;
304
+ case 'user-defined':
305
+ o.user_id = d.userId;
306
+ o.packet_type = d.packetType;
307
+ put(o, 'data', d.data);
308
+ break;
309
+ case 'test':
310
+ put(o, 'data', d.data);
311
+ break;
312
+ case 'agrelo-df':
313
+ o.bearing_degrees = d.bearingDegrees;
314
+ o.quality = d.quality;
315
+ break;
316
+ case 'unrecognized':
317
+ o.reason = d.reason;
318
+ break;
319
+ }
320
+ return o;
321
+ }
322
+
323
+ function toNeutralInner(p: AprsPacket): Neutral {
324
+ const o: Neutral = { source: p.source, destination: p.destination };
325
+ put(o, 'path', p.path.map(pathEntryN));
326
+ o.data = toNeutralData(p.data);
327
+ put(o, 'diagnostics', p.diagnostics.map(formatDiagnostic));
328
+ return o;
329
+ }
330
+
331
+ /** A packet's header in the neutral form. */
332
+ export function toNeutralHeader(p: AprsPacket): Neutral {
333
+ const o: Neutral = { source: p.source, destination: p.destination };
334
+ put(o, 'path', p.path.map(pathEntryN));
335
+ if (p.qConstruct) {
336
+ const q: Neutral = { construct: p.qConstruct.construct };
337
+ put(q, 'station', p.qConstruct.station);
338
+ o.q_construct = q;
339
+ }
340
+ return o;
341
+ }
342
+
343
+ /** A packet in the neutral form: `{header, data, diagnostics}`. */
344
+ export function toNeutralPacket(p: AprsPacket): Neutral {
345
+ const o: Neutral = { header: toNeutralHeader(p), data: toNeutralData(p.data) };
346
+ put(o, 'diagnostics', p.diagnostics.map(formatDiagnostic));
347
+ return o;
348
+ }
349
+
350
+ // ---- from the neutral form (for encode cases)
351
+
352
+ type J = Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
353
+
354
+ function symbolFrom(text: unknown): AprsSymbol {
355
+ const t = String(text);
356
+ return { table: t.slice(0, 1), code: t.slice(1) };
357
+ }
358
+
359
+ function timestampFrom(text: unknown): AprsTimestamp {
360
+ const t = parseTimestamp(String(text));
361
+ if (!t) throw new RangeError(`not a timestamp: ${String(text)}`);
362
+ return t;
363
+ }
364
+
365
+ function opt<T>(o: Record<string, unknown>, key: string, value: T | undefined): void {
366
+ if (value !== undefined) o[key] = value;
367
+ }
368
+
369
+ function weatherFrom(w: J): Weather {
370
+ const o: Record<string, unknown> = {};
371
+ opt(o, 'windDirectionDegrees', w.wind_direction_degrees);
372
+ opt(o, 'windSpeedMph', w.wind_speed_mph);
373
+ opt(o, 'windGustMph', w.wind_gust_mph);
374
+ opt(o, 'temperatureF', w.temperature_f);
375
+ opt(o, 'rain1hIn', w.rain_1h_in);
376
+ opt(o, 'rain24hIn', w.rain_24h_in);
377
+ opt(o, 'rainMidnightIn', w.rain_midnight_in);
378
+ opt(o, 'rainRaw', w.rain_raw);
379
+ opt(o, 'humidityPercent', w.humidity_percent);
380
+ opt(o, 'pressureMbar', w.pressure_mbar);
381
+ opt(o, 'luminosityWM2', w.luminosity_w_m2);
382
+ opt(o, 'snow24hIn', w.snow_24h_in);
383
+ opt(o, 'software', w.software);
384
+ opt(o, 'unit', w.unit);
385
+ if (Array.isArray(w.extra)) o.extra = w.extra.map((e: J) => ({ letter: e.letter, value: e.value }));
386
+ return o as Weather;
387
+ }
388
+
389
+ function positionedFrom(o: Record<string, unknown>, d: J): void {
390
+ o.latitude = d.latitude;
391
+ o.longitude = d.longitude;
392
+ opt(o, 'ambiguity', d.ambiguity);
393
+ o.symbol = symbolFrom(d.symbol);
394
+ if (d.compressed) o.compressed = true;
395
+ if (d.compression) o.compression = { fix: d.compression.fix, source: d.compression.source, origin: d.compression.origin };
396
+ opt(o, 'courseDegrees', d.course_degrees);
397
+ opt(o, 'speedKnots', d.speed_knots);
398
+ opt(o, 'altitudeFeet', d.altitude_feet);
399
+ if (d.phg) {
400
+ const p: Record<string, unknown> = { power: d.phg.power, height: d.phg.height, gain: d.phg.gain, directivity: d.phg.directivity };
401
+ opt(p, 'beaconsPerHour', d.phg.beacons_per_hour);
402
+ o.phg = p;
403
+ }
404
+ opt(o, 'rangeMiles', d.range_miles);
405
+ if (d.dfs) o.dfs = { strength: d.dfs.strength, height: d.dfs.height, gain: d.dfs.gain, directivity: d.dfs.directivity };
406
+ if (d.area) {
407
+ const a: Record<string, unknown> = { shape: d.area.shape, color: d.area.color, latOffset: d.area.lat_offset, lonOffset: d.area.lon_offset };
408
+ opt(a, 'corridorWidthMiles', d.area.corridor_width_miles);
409
+ o.area = a;
410
+ }
411
+ if (d.df_bearing)
412
+ o.dfBearing = {
413
+ bearingDegrees: d.df_bearing.bearing_degrees,
414
+ number: d.df_bearing.number,
415
+ range: d.df_bearing.range,
416
+ quality: d.df_bearing.quality,
417
+ };
418
+ if (d.storm) {
419
+ const s: Record<string, unknown> = { type: d.storm.type };
420
+ opt(s, 'sustainedWindKnots', d.storm.sustained_wind_knots);
421
+ opt(s, 'gustKnots', d.storm.gust_knots);
422
+ opt(s, 'centralPressureMbar', d.storm.central_pressure_mbar);
423
+ opt(s, 'hurricaneRadiusNm', d.storm.hurricane_radius_nm);
424
+ opt(s, 'tropicalStormRadiusNm', d.storm.tropical_storm_radius_nm);
425
+ opt(s, 'wholeGaleRadiusNm', d.storm.whole_gale_radius_nm);
426
+ o.storm = s;
427
+ }
428
+ if (d.dao) o.dao = { datum: d.dao.datum, precision: d.dao.precision };
429
+ if (d.telemetry) {
430
+ const t: Record<string, unknown> = { sequence: d.telemetry.sequence, analog: [...d.telemetry.analog] };
431
+ opt(t, 'digital', d.telemetry.digital);
432
+ o.telemetry = t;
433
+ }
434
+ if (d.frequency) {
435
+ const f: Record<string, unknown> = { mhz: d.frequency.mhz };
436
+ opt(f, 'tone', d.frequency.tone);
437
+ opt(f, 'toneValue', d.frequency.tone_value);
438
+ opt(f, 'offsetKhz', d.frequency.offset_khz);
439
+ opt(f, 'range', d.frequency.range);
440
+ if (d.frequency.range_km) f.rangeKm = true;
441
+ if (d.frequency.narrow) f.narrow = true;
442
+ if (d.frequency.ten_khz_resolution) f.tenKhzResolution = true;
443
+ o.frequency = f;
444
+ }
445
+ if (d.weather) o.weather = weatherFrom(d.weather);
446
+ opt(o, 'signpost', d.signpost);
447
+ opt(o, 'comment', d.comment);
448
+ }
449
+
450
+ /** Data from the neutral form. */
451
+ export function fromNeutralData(d: J): AprsData {
452
+ const o: Record<string, unknown> = { type: d.type };
453
+ switch (d.type as AprsData['type']) {
454
+ case 'position':
455
+ if (d.timestamp !== undefined) o.timestamp = timestampFrom(d.timestamp);
456
+ if (d.messaging) o.messaging = true;
457
+ positionedFrom(o, d);
458
+ break;
459
+ case 'mic-e':
460
+ o.micEMessage = d.mic_e_message;
461
+ if (d.old_data) o.oldData = true;
462
+ opt(o, 'typeCode', d.type_code);
463
+ opt(o, 'deviceSuffix', d.device_suffix);
464
+ opt(o, 'locator', d.locator);
465
+ opt(o, 'legacyTelemetry', d.legacy_telemetry);
466
+ opt(o, 'destinationSsid', d.destination_ssid);
467
+ positionedFrom(o, d);
468
+ break;
469
+ case 'object':
470
+ o.name = d.name;
471
+ if (d.killed) o.killed = true;
472
+ if (d.timestamp !== undefined) o.timestamp = timestampFrom(d.timestamp);
473
+ positionedFrom(o, d);
474
+ break;
475
+ case 'item':
476
+ o.name = d.name;
477
+ if (d.killed) o.killed = true;
478
+ positionedFrom(o, d);
479
+ break;
480
+ case 'message':
481
+ o.addressee = d.addressee ?? '';
482
+ o.text = d.text ?? '';
483
+ opt(o, 'messageId', d.message_id);
484
+ opt(o, 'replyAck', d.reply_ack);
485
+ break;
486
+ case 'ack':
487
+ o.addressee = d.addressee ?? '';
488
+ o.ackedId = d.acked_id ?? '';
489
+ opt(o, 'replyAck', d.reply_ack);
490
+ opt(o, 'messageId', d.message_id);
491
+ break;
492
+ case 'reject':
493
+ o.addressee = d.addressee ?? '';
494
+ o.rejectedId = d.rejected_id ?? '';
495
+ opt(o, 'replyAck', d.reply_ack);
496
+ opt(o, 'messageId', d.message_id);
497
+ break;
498
+ case 'bulletin':
499
+ case 'nws-bulletin':
500
+ o.addressee = d.addressee ?? '';
501
+ o.text = d.text ?? '';
502
+ opt(o, 'messageId', d.message_id);
503
+ break;
504
+ case 'telemetry-names':
505
+ o.addressee = d.addressee ?? '';
506
+ o.names = d.names ?? [];
507
+ opt(o, 'messageId', d.message_id);
508
+ break;
509
+ case 'telemetry-units':
510
+ o.addressee = d.addressee ?? '';
511
+ o.units = d.units ?? [];
512
+ opt(o, 'messageId', d.message_id);
513
+ break;
514
+ case 'telemetry-coefficients':
515
+ o.addressee = d.addressee ?? '';
516
+ o.coefficients = d.coefficients ?? [];
517
+ opt(o, 'messageId', d.message_id);
518
+ break;
519
+ case 'telemetry-bits':
520
+ o.addressee = d.addressee ?? '';
521
+ o.bits = d.bits ?? '';
522
+ opt(o, 'project', d.project);
523
+ opt(o, 'messageId', d.message_id);
524
+ break;
525
+ case 'directed-query':
526
+ o.addressee = d.addressee ?? '';
527
+ o.queryType = d.query_type ?? '';
528
+ opt(o, 'target', d.target);
529
+ break;
530
+ case 'status':
531
+ if (d.timestamp !== undefined) o.timestamp = timestampFrom(d.timestamp);
532
+ opt(o, 'locator', d.locator);
533
+ if (d.symbol !== undefined) o.symbol = symbolFrom(d.symbol);
534
+ if (d.beam) o.beam = { headingCode: d.beam.heading_code, powerCode: d.beam.power_code };
535
+ opt(o, 'text', d.text);
536
+ break;
537
+ case 'telemetry':
538
+ o.sequence = d.sequence ?? '';
539
+ o.analog = d.analog ?? [];
540
+ opt(o, 'bits', d.bits);
541
+ opt(o, 'comment', d.comment);
542
+ break;
543
+ case 'weather':
544
+ if (d.timestamp !== undefined) o.timestamp = timestampFrom(d.timestamp);
545
+ o.weather = weatherFrom(d.weather ?? {});
546
+ opt(o, 'comment', d.comment);
547
+ break;
548
+ case 'raw-weather':
549
+ o.format = d.format;
550
+ opt(o, 'data', d.data);
551
+ break;
552
+ case 'nmea':
553
+ o.sentence = d.sentence ?? '';
554
+ if (d.has_checksum) o.hasChecksum = true;
555
+ opt(o, 'latitude', d.latitude);
556
+ opt(o, 'longitude', d.longitude);
557
+ opt(o, 'fix', d.fix);
558
+ opt(o, 'courseDegrees', d.course_degrees);
559
+ opt(o, 'speedKnots', d.speed_knots);
560
+ opt(o, 'altitudeM', d.altitude_m);
561
+ opt(o, 'time', d.time);
562
+ opt(o, 'waypoint', d.waypoint);
563
+ break;
564
+ case 'maidenhead-beacon':
565
+ o.locator = d.locator ?? '';
566
+ opt(o, 'comment', d.comment);
567
+ break;
568
+ case 'query':
569
+ o.queryType = d.query_type ?? '';
570
+ if (d.footprint)
571
+ o.footprint = { latitude: d.footprint.latitude, longitude: d.footprint.longitude, radiusMiles: d.footprint.radius_miles };
572
+ break;
573
+ case 'capabilities':
574
+ o.capabilities = d.capabilities ?? [];
575
+ break;
576
+ case 'user-defined':
577
+ o.userId = d.user_id;
578
+ o.packetType = d.packet_type;
579
+ opt(o, 'data', d.data);
580
+ break;
581
+ case 'test':
582
+ opt(o, 'data', d.data);
583
+ break;
584
+ case 'agrelo-df':
585
+ o.bearingDegrees = d.bearing_degrees;
586
+ o.quality = d.quality;
587
+ break;
588
+ case 'unrecognized':
589
+ o.reason = d.reason;
590
+ break;
591
+ case 'third-party':
592
+ throw new RangeError('third-party data cannot be built from the neutral form');
593
+ }
594
+ return o as unknown as AprsData;
595
+ }