@aguacerowx/mapsgl 0.0.54 → 0.0.56
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/index.js +5 -6
- package/package.json +3 -3
- package/src/NexradWeatherController.js +11 -1
- package/src/WeatherLayerManager.js +3 -5
- package/src/nexrad/loadNexradSites.ts +85 -7
- package/src/nexrad/nexradArchiveDiag.ts +26 -0
- package/src/nexrad/nexradMapboxFrameOpts.bundled.js +3 -2
- package/src/nexrad/nexradMapboxFrameOpts.ts +3 -2
- package/src/nexrad/nexradSitesDefault.json +1700 -0
- package/src/nexrad/radarArchiveCore.bundled.js +2724 -37
- package/src/nexrad/radarArchiveCore.ts +149 -30
- package/src/nexrad/radarDecode.worker.bundled.js +130 -126
- package/src/nexrad/radarDecode.worker.ts +13 -215
- package/src/nexrad/radarDecodeSlot.ts +195 -0
- package/src/NwsWatchesWarningsOverlay.js +0 -973
- package/src/nwsAlertsFetchSpec.js +0 -114
- package/src/nwsAlertsSupport.js +0 -1337
- package/src/nwsEventColorsDefaults.js +0 -133
- package/src/nwsSdkConstants.js +0 -360
- package/src/nwsWarningCustomizationKey.gen.js +0 -493
|
@@ -1,227 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type NexradSite } from './PreprocessedSweepParser.js';
|
|
1
|
+
import { decodeRadarSlotMessage, type DecodeSlotRequest } from './radarDecodeSlot.js';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
| {
|
|
6
|
-
type: 'DECODE_SLOT';
|
|
7
|
-
requestId: number;
|
|
8
|
-
objectKey: string;
|
|
9
|
-
slotBuffer: ArrayBuffer; // compressed slot blob
|
|
10
|
-
nRays: number;
|
|
11
|
-
nGates: number;
|
|
12
|
-
firstGateKm: number;
|
|
13
|
-
gateWidthKm: number;
|
|
14
|
-
azimuthsBuffer: ArrayBuffer; // Float32Array big-endian from file header
|
|
15
|
-
sites: NexradSite[];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type DecodeResponse = {
|
|
19
|
-
type: 'DECODE_RESULT';
|
|
20
|
-
requestId: number;
|
|
21
|
-
gateData: Uint8Array | null;
|
|
22
|
-
stationLat?: number;
|
|
23
|
-
stationLon?: number;
|
|
24
|
-
firstGateKm?: number;
|
|
25
|
-
gateWidthKm?: number;
|
|
26
|
-
valueScale?: number;
|
|
27
|
-
valueOffset?: number;
|
|
28
|
-
rayBoundariesDeg?: Float32Array;
|
|
29
|
-
nRays?: number;
|
|
30
|
-
nGates?: number;
|
|
31
|
-
error?: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const SLOT_HDR_BYTES = 28;
|
|
35
|
-
|
|
36
|
-
function isZstd(bytes: Uint8Array): boolean {
|
|
37
|
-
return bytes.length >= 4 &&
|
|
38
|
-
bytes[0] === 0x28 && bytes[1] === 0xb5 && bytes[2] === 0x2f && bytes[3] === 0xfd;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseObjectKey(objectKey: string): { stationId: string } | null {
|
|
42
|
-
const parts = objectKey.split('_');
|
|
43
|
-
if (parts.length < 2) return null;
|
|
44
|
-
return { stationId: parts[0] };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Calculates robust boundaries between rays.
|
|
49
|
-
* Using midpoints and a fallback spacing prevents precision gaps at the 0/360 seam
|
|
50
|
-
* which often causes "flashing" during map transforms.
|
|
51
|
-
*/
|
|
52
|
-
function buildRayBoundariesDeg(azimuths: Float32Array): Float32Array {
|
|
53
|
-
const nRays = azimuths.length;
|
|
54
|
-
const boundaries = new Float32Array(nRays + 1);
|
|
55
|
-
if (nRays === 0) return boundaries;
|
|
56
|
-
|
|
57
|
-
let totalSpacing = 0;
|
|
58
|
-
let validCount = 0;
|
|
59
|
-
for (let i = 1; i < nRays; i++) {
|
|
60
|
-
let diff = azimuths[i] - azimuths[i - 1];
|
|
61
|
-
while (diff < -180) diff += 360;
|
|
62
|
-
while (diff > 180) diff -= 360;
|
|
63
|
-
const absDiff = Math.abs(diff);
|
|
64
|
-
if (absDiff > 0 && absDiff < 10) {
|
|
65
|
-
totalSpacing += absDiff;
|
|
66
|
-
validCount++;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let avgSpacing = (validCount > 0) ? (totalSpacing / validCount) : (360 / nRays);
|
|
71
|
-
if (avgSpacing <= 0 || isNaN(avgSpacing)) avgSpacing = 1.0;
|
|
72
|
-
|
|
73
|
-
boundaries[0] = azimuths[0] - (avgSpacing / 2);
|
|
74
|
-
for (let i = 1; i < nRays; i++) {
|
|
75
|
-
let diff = azimuths[i] - azimuths[i - 1];
|
|
76
|
-
while (diff < -180) diff += 360;
|
|
77
|
-
while (diff > 180) diff -= 360;
|
|
78
|
-
boundaries[i] = azimuths[i - 1] + (diff / 2);
|
|
79
|
-
}
|
|
80
|
-
boundaries[nRays] = azimuths[nRays - 1] + (avgSpacing / 2);
|
|
81
|
-
|
|
82
|
-
return boundaries;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
self.onmessage = (event: MessageEvent<DecodeRequest>) => {
|
|
3
|
+
self.onmessage = (event: MessageEvent<DecodeSlotRequest>) => {
|
|
86
4
|
const data = event.data;
|
|
87
5
|
if (!data || data.type !== 'DECODE_SLOT') return;
|
|
88
6
|
|
|
89
|
-
const { requestId, objectKey, slotBuffer, nRays, nGates,
|
|
90
|
-
firstGateKm, gateWidthKm, azimuthsBuffer, sites } = data;
|
|
91
|
-
|
|
92
7
|
try {
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
self.postMessage(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const decompressed = decompressZstd(compressed);
|
|
101
|
-
const buffer = decompressed.buffer.slice(
|
|
102
|
-
decompressed.byteOffset,
|
|
103
|
-
decompressed.byteOffset + decompressed.byteLength,
|
|
104
|
-
) as ArrayBuffer;
|
|
105
|
-
|
|
106
|
-
const view = new DataView(buffer);
|
|
107
|
-
const valueScale = view.getFloat32(0, false);
|
|
108
|
-
const valueOffset = view.getFloat32(4, false);
|
|
109
|
-
const present = view.getUint32(8, false);
|
|
110
|
-
|
|
111
|
-
if (present === 0) {
|
|
112
|
-
self.postMessage({ type: 'DECODE_RESULT', requestId, gateData: null,
|
|
113
|
-
error: `Slot for ${objectKey} marked not present` });
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const slotNRays = view.getUint16(12, false);
|
|
118
|
-
const slotNGates = view.getUint16(14, false);
|
|
119
|
-
|
|
120
|
-
const gateBytes = buffer.byteLength - SLOT_HDR_BYTES;
|
|
121
|
-
const expectedGates = slotNRays * slotNGates;
|
|
122
|
-
const bytesPerGate = gateBytes / expectedGates;
|
|
123
|
-
|
|
124
|
-
if (bytesPerGate !== 1 && bytesPerGate !== 2 && bytesPerGate !== 4) {
|
|
125
|
-
self.postMessage({ type: 'DECODE_RESULT', requestId, gateData: null,
|
|
126
|
-
error: `Unexpected bytesPerGate=${bytesPerGate} for ${objectKey}` });
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const keyParts = parseObjectKey(objectKey);
|
|
131
|
-
if (!keyParts) {
|
|
132
|
-
self.postMessage({ type: 'DECODE_RESULT', requestId, gateData: null,
|
|
133
|
-
error: `Unable to parse station from key: ${objectKey}` });
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const site = sites.find(s => s.id === keyParts.stationId);
|
|
138
|
-
if (!site) {
|
|
139
|
-
self.postMessage({ type: 'DECODE_RESULT', requestId, gateData: null,
|
|
140
|
-
error: `Station "${keyParts.stationId}" not found` });
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* FIX: We MUST use slotNRays (the actual data count) to build geometry.
|
|
146
|
-
* Using the header's nRays when the slot contains a different count
|
|
147
|
-
* creates UV misalignment that causes flickering on map movement.
|
|
148
|
-
*/
|
|
149
|
-
const azimuths = new Float32Array(slotNRays);
|
|
150
|
-
const azView = new DataView(azimuthsBuffer);
|
|
151
|
-
const headerNRays = azimuthsBuffer.byteLength / 4;
|
|
152
|
-
|
|
153
|
-
for (let i = 0; i < slotNRays; i++) {
|
|
154
|
-
if (i < headerNRays) {
|
|
155
|
-
azimuths[i] = azView.getFloat32(i * 4, false);
|
|
156
|
-
} else {
|
|
157
|
-
// Fallback for edge cases where data exceeds metadata
|
|
158
|
-
const prevAz = i > 0 ? azimuths[i - 1] : 0;
|
|
159
|
-
azimuths[i] = (prevAz + (360 / slotNRays)) % 360;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const rayBoundariesDeg = buildRayBoundariesDeg(azimuths);
|
|
164
|
-
|
|
165
|
-
const gateCount = slotNRays * slotNGates;
|
|
166
|
-
const gateDataCopy = new Uint8Array(slotNRays * slotNGates * 2);
|
|
167
|
-
const outView = new DataView(gateDataCopy.buffer);
|
|
168
|
-
|
|
169
|
-
if (bytesPerGate === 1) {
|
|
170
|
-
const raw = new Uint8Array(buffer, SLOT_HDR_BYTES, gateCount);
|
|
171
|
-
for (let ray = 0; ray < slotNRays; ray++) {
|
|
172
|
-
let prev = 0;
|
|
173
|
-
for (let g = 0; g < slotNGates; g++) {
|
|
174
|
-
const idx = ray * slotNGates + g;
|
|
175
|
-
const delta = raw[idx];
|
|
176
|
-
const val = (prev + delta) & 0xFF;
|
|
177
|
-
prev = val;
|
|
178
|
-
if (val === 0) {
|
|
179
|
-
outView.setInt16(idx * 2, -32768, false);
|
|
180
|
-
} else {
|
|
181
|
-
outView.setInt16(idx * 2, val, false);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
8
|
+
const response = decodeRadarSlotMessage(data);
|
|
9
|
+
if (response.gateData && response.rayBoundariesDeg) {
|
|
10
|
+
(self as unknown as Worker).postMessage(response, [
|
|
11
|
+
response.gateData.buffer as ArrayBuffer,
|
|
12
|
+
response.rayBoundariesDeg.buffer as ArrayBuffer,
|
|
13
|
+
]);
|
|
185
14
|
} else {
|
|
186
|
-
|
|
187
|
-
for (let ray = 0; ray < slotNRays; ray++) {
|
|
188
|
-
let prev = 0;
|
|
189
|
-
for (let g = 0; g < slotNGates; g++) {
|
|
190
|
-
const idx = ray * slotNGates + g;
|
|
191
|
-
const hi = rawBytes[idx * 2];
|
|
192
|
-
const lo = rawBytes[idx * 2 + 1];
|
|
193
|
-
const delta = (hi << 8 | lo) << 16 >> 16;
|
|
194
|
-
const val = (prev + delta) | 0;
|
|
195
|
-
prev = val;
|
|
196
|
-
outView.setInt16(idx * 2, val, false);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
15
|
+
self.postMessage(response);
|
|
199
16
|
}
|
|
200
|
-
|
|
201
|
-
const response: DecodeResponse = {
|
|
202
|
-
type: 'DECODE_RESULT',
|
|
203
|
-
requestId,
|
|
204
|
-
gateData: gateDataCopy,
|
|
205
|
-
stationLat: site.lat,
|
|
206
|
-
stationLon: site.lon,
|
|
207
|
-
firstGateKm,
|
|
208
|
-
gateWidthKm,
|
|
209
|
-
valueScale,
|
|
210
|
-
valueOffset,
|
|
211
|
-
rayBoundariesDeg,
|
|
212
|
-
nRays: slotNRays,
|
|
213
|
-
nGates: slotNGates,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Transfer the buffers to the main thread to avoid copying overhead
|
|
217
|
-
(self as any).postMessage(response, [
|
|
218
|
-
gateDataCopy.buffer as ArrayBuffer,
|
|
219
|
-
rayBoundariesDeg.buffer as ArrayBuffer,
|
|
220
|
-
]);
|
|
221
17
|
} catch (error) {
|
|
222
18
|
self.postMessage({
|
|
223
|
-
type: 'DECODE_RESULT',
|
|
19
|
+
type: 'DECODE_RESULT',
|
|
20
|
+
requestId: data.requestId,
|
|
21
|
+
gateData: null,
|
|
224
22
|
error: error instanceof Error ? error.message : 'Unknown worker error',
|
|
225
23
|
});
|
|
226
24
|
}
|
|
227
|
-
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { decompress as decompressZstd } from 'fzstd';
|
|
2
|
+
import type { NexradSite } from './PreprocessedSweepParser.js';
|
|
3
|
+
|
|
4
|
+
export type DecodeSlotRequest = {
|
|
5
|
+
type: 'DECODE_SLOT';
|
|
6
|
+
requestId: number;
|
|
7
|
+
objectKey: string;
|
|
8
|
+
slotBuffer: ArrayBuffer;
|
|
9
|
+
nRays: number;
|
|
10
|
+
nGates: number;
|
|
11
|
+
firstGateKm: number;
|
|
12
|
+
gateWidthKm: number;
|
|
13
|
+
azimuthsBuffer: ArrayBuffer;
|
|
14
|
+
sites: NexradSite[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type DecodeSlotResponse = {
|
|
18
|
+
type: 'DECODE_RESULT';
|
|
19
|
+
requestId: number;
|
|
20
|
+
gateData: Uint8Array | null;
|
|
21
|
+
stationLat?: number;
|
|
22
|
+
stationLon?: number;
|
|
23
|
+
firstGateKm?: number;
|
|
24
|
+
gateWidthKm?: number;
|
|
25
|
+
valueScale?: number;
|
|
26
|
+
valueOffset?: number;
|
|
27
|
+
rayBoundariesDeg?: Float32Array;
|
|
28
|
+
nRays?: number;
|
|
29
|
+
nGates?: number;
|
|
30
|
+
error?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const SLOT_HDR_BYTES = 28;
|
|
34
|
+
|
|
35
|
+
function isZstd(bytes: Uint8Array): boolean {
|
|
36
|
+
return bytes.length >= 4 &&
|
|
37
|
+
bytes[0] === 0x28 && bytes[1] === 0xb5 && bytes[2] === 0x2f && bytes[3] === 0xfd;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseObjectKey(objectKey: string): { stationId: string } | null {
|
|
41
|
+
const parts = objectKey.split('_');
|
|
42
|
+
if (parts.length < 2) return null;
|
|
43
|
+
return { stationId: parts[0] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildRayBoundariesDeg(azimuths: Float32Array): Float32Array {
|
|
47
|
+
const nRays = azimuths.length;
|
|
48
|
+
const boundaries = new Float32Array(nRays + 1);
|
|
49
|
+
if (nRays === 0) return boundaries;
|
|
50
|
+
|
|
51
|
+
let totalSpacing = 0;
|
|
52
|
+
let validCount = 0;
|
|
53
|
+
for (let i = 1; i < nRays; i++) {
|
|
54
|
+
let diff = azimuths[i] - azimuths[i - 1];
|
|
55
|
+
while (diff < -180) diff += 360;
|
|
56
|
+
while (diff > 180) diff -= 360;
|
|
57
|
+
const absDiff = Math.abs(diff);
|
|
58
|
+
if (absDiff > 0 && absDiff < 10) {
|
|
59
|
+
totalSpacing += absDiff;
|
|
60
|
+
validCount++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let avgSpacing = (validCount > 0) ? (totalSpacing / validCount) : (360 / nRays);
|
|
65
|
+
if (avgSpacing <= 0 || isNaN(avgSpacing)) avgSpacing = 1.0;
|
|
66
|
+
|
|
67
|
+
boundaries[0] = azimuths[0] - (avgSpacing / 2);
|
|
68
|
+
for (let i = 1; i < nRays; i++) {
|
|
69
|
+
let diff = azimuths[i] - azimuths[i - 1];
|
|
70
|
+
while (diff < -180) diff += 360;
|
|
71
|
+
while (diff > 180) diff -= 360;
|
|
72
|
+
boundaries[i] = azimuths[i - 1] + (diff / 2);
|
|
73
|
+
}
|
|
74
|
+
boundaries[nRays] = azimuths[nRays - 1] + (avgSpacing / 2);
|
|
75
|
+
|
|
76
|
+
return boundaries;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Shared decode path for the dedicated worker and React Native (main-thread shim). */
|
|
80
|
+
export function decodeRadarSlotMessage(data: DecodeSlotRequest): DecodeSlotResponse {
|
|
81
|
+
const { requestId, objectKey, slotBuffer, nRays, nGates,
|
|
82
|
+
firstGateKm, gateWidthKm, azimuthsBuffer, sites } = data;
|
|
83
|
+
|
|
84
|
+
const compressed = new Uint8Array(slotBuffer);
|
|
85
|
+
if (!isZstd(compressed)) {
|
|
86
|
+
return { type: 'DECODE_RESULT', requestId, gateData: null,
|
|
87
|
+
error: `Slot for ${objectKey} is not valid zstd` };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const decompressed = decompressZstd(compressed);
|
|
91
|
+
const buffer = decompressed.buffer.slice(
|
|
92
|
+
decompressed.byteOffset,
|
|
93
|
+
decompressed.byteOffset + decompressed.byteLength,
|
|
94
|
+
) as ArrayBuffer;
|
|
95
|
+
|
|
96
|
+
const view = new DataView(buffer);
|
|
97
|
+
const valueScale = view.getFloat32(0, false);
|
|
98
|
+
const valueOffset = view.getFloat32(4, false);
|
|
99
|
+
const present = view.getUint32(8, false);
|
|
100
|
+
|
|
101
|
+
if (present === 0) {
|
|
102
|
+
return { type: 'DECODE_RESULT', requestId, gateData: null,
|
|
103
|
+
error: `Slot for ${objectKey} marked not present` };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const slotNRays = view.getUint16(12, false);
|
|
107
|
+
const slotNGates = view.getUint16(14, false);
|
|
108
|
+
|
|
109
|
+
const gateBytes = buffer.byteLength - SLOT_HDR_BYTES;
|
|
110
|
+
const expectedGates = slotNRays * slotNGates;
|
|
111
|
+
const bytesPerGate = gateBytes / expectedGates;
|
|
112
|
+
|
|
113
|
+
if (bytesPerGate !== 1 && bytesPerGate !== 2 && bytesPerGate !== 4) {
|
|
114
|
+
return { type: 'DECODE_RESULT', requestId, gateData: null,
|
|
115
|
+
error: `Unexpected bytesPerGate=${bytesPerGate} for ${objectKey}` };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const keyParts = parseObjectKey(objectKey);
|
|
119
|
+
if (!keyParts) {
|
|
120
|
+
return { type: 'DECODE_RESULT', requestId, gateData: null,
|
|
121
|
+
error: `Unable to parse station from key: ${objectKey}` };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const site = sites.find(s => s.id === keyParts.stationId);
|
|
125
|
+
if (!site) {
|
|
126
|
+
return { type: 'DECODE_RESULT', requestId, gateData: null,
|
|
127
|
+
error: `Station "${keyParts.stationId}" not found` };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const azimuths = new Float32Array(slotNRays);
|
|
131
|
+
const azView = new DataView(azimuthsBuffer);
|
|
132
|
+
const headerNRays = azimuthsBuffer.byteLength / 4;
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i < slotNRays; i++) {
|
|
135
|
+
if (i < headerNRays) {
|
|
136
|
+
azimuths[i] = azView.getFloat32(i * 4, false);
|
|
137
|
+
} else {
|
|
138
|
+
const prevAz = i > 0 ? azimuths[i - 1] : 0;
|
|
139
|
+
azimuths[i] = (prevAz + (360 / slotNRays)) % 360;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rayBoundariesDeg = buildRayBoundariesDeg(azimuths);
|
|
144
|
+
|
|
145
|
+
const gateCount = slotNRays * slotNGates;
|
|
146
|
+
const gateDataCopy = new Uint8Array(slotNRays * slotNGates * 2);
|
|
147
|
+
const outView = new DataView(gateDataCopy.buffer);
|
|
148
|
+
|
|
149
|
+
if (bytesPerGate === 1) {
|
|
150
|
+
const raw = new Uint8Array(buffer, SLOT_HDR_BYTES, gateCount);
|
|
151
|
+
for (let ray = 0; ray < slotNRays; ray++) {
|
|
152
|
+
let prev = 0;
|
|
153
|
+
for (let g = 0; g < slotNGates; g++) {
|
|
154
|
+
const idx = ray * slotNGates + g;
|
|
155
|
+
const delta = raw[idx];
|
|
156
|
+
const val = (prev + delta) & 0xFF;
|
|
157
|
+
prev = val;
|
|
158
|
+
if (val === 0) {
|
|
159
|
+
outView.setInt16(idx * 2, -32768, false);
|
|
160
|
+
} else {
|
|
161
|
+
outView.setInt16(idx * 2, val, false);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
const rawBytes = new Uint8Array(buffer, SLOT_HDR_BYTES, gateCount * 2);
|
|
167
|
+
for (let ray = 0; ray < slotNRays; ray++) {
|
|
168
|
+
let prev = 0;
|
|
169
|
+
for (let g = 0; g < slotNGates; g++) {
|
|
170
|
+
const idx = ray * slotNGates + g;
|
|
171
|
+
const hi = rawBytes[idx * 2];
|
|
172
|
+
const lo = rawBytes[idx * 2 + 1];
|
|
173
|
+
const delta = (hi << 8 | lo) << 16 >> 16;
|
|
174
|
+
const val = (prev + delta) | 0;
|
|
175
|
+
prev = val;
|
|
176
|
+
outView.setInt16(idx * 2, val, false);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
type: 'DECODE_RESULT',
|
|
183
|
+
requestId,
|
|
184
|
+
gateData: gateDataCopy,
|
|
185
|
+
stationLat: site.lat,
|
|
186
|
+
stationLon: site.lon,
|
|
187
|
+
firstGateKm,
|
|
188
|
+
gateWidthKm,
|
|
189
|
+
valueScale,
|
|
190
|
+
valueOffset,
|
|
191
|
+
rayBoundariesDeg,
|
|
192
|
+
nRays: slotNRays,
|
|
193
|
+
nGates: slotNGates,
|
|
194
|
+
};
|
|
195
|
+
}
|