@aguacerowx/mapsgl 0.0.57 → 0.0.58

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.
@@ -5611,12 +5611,17 @@ function isAbortError(err2) {
5611
5611
  }
5612
5612
  var NEXRAD_ARCHIVE_API_KEY = "";
5613
5613
  var NEXRAD_ARCHIVE_BUNDLE_ID = "";
5614
+ var NEXRAD_ARCHIVE_SITE_ORIGIN = "";
5614
5615
  function setNexradArchiveApiKey(k) {
5615
5616
  NEXRAD_ARCHIVE_API_KEY = k || "";
5616
5617
  }
5617
5618
  function setNexradArchiveBundleId(bundleId) {
5618
5619
  NEXRAD_ARCHIVE_BUNDLE_ID = bundleId || "";
5619
5620
  }
5621
+ function setNexradArchiveSiteOrigin(origin) {
5622
+ const raw = typeof origin === "string" ? origin.trim() : "";
5623
+ NEXRAD_ARCHIVE_SITE_ORIGIN = raw ? raw.replace(/\/+$/, "") : "";
5624
+ }
5620
5625
  function cloudFrontUrlWithApiKeyQuery(baseUrl) {
5621
5626
  if (!NEXRAD_ARCHIVE_API_KEY) return baseUrl;
5622
5627
  const sep = baseUrl.includes("?") ? "&" : "?";
@@ -5633,6 +5638,10 @@ function level2CloudFrontFetchHeaders(range) {
5633
5638
  if (g.navigator?.product === "ReactNative" && NEXRAD_ARCHIVE_BUNDLE_ID) {
5634
5639
  headers["x-app-identifier"] = NEXRAD_ARCHIVE_BUNDLE_ID;
5635
5640
  }
5641
+ if (NEXRAD_ARCHIVE_SITE_ORIGIN) {
5642
+ headers["Origin"] = NEXRAD_ARCHIVE_SITE_ORIGIN;
5643
+ headers["Referer"] = `${NEXRAD_ARCHIVE_SITE_ORIGIN}/`;
5644
+ }
5636
5645
  return headers;
5637
5646
  }
5638
5647
  function shouldDecodeRadarOnMainThread() {
@@ -7054,6 +7063,7 @@ export {
7054
7063
  objectKeyToUrl,
7055
7064
  setNexradArchiveApiKey,
7056
7065
  setNexradArchiveBundleId,
7066
+ setNexradArchiveSiteOrigin,
7057
7067
  setNexradSitesFetchAuth,
7058
7068
  setNexradSitesJsonUrl
7059
7069
  };
@@ -48,6 +48,8 @@ function isAbortError(err: unknown): boolean {
48
48
  let NEXRAD_ARCHIVE_API_KEY = '';
49
49
  /** Same as {@link AguaceroCore} grid fetches: `x-app-identifier` on React Native when set. */
50
50
  let NEXRAD_ARCHIVE_BUNDLE_ID = '';
51
+ /** Same as {@link AguaceroCore#gridRequestSiteOrigin}: CloudFront often allowlists Origin/Referer (RN has no browser default). */
52
+ let NEXRAD_ARCHIVE_SITE_ORIGIN = '';
51
53
 
52
54
  export function setNexradArchiveApiKey(k: string) {
53
55
  NEXRAD_ARCHIVE_API_KEY = k || '';
@@ -57,6 +59,11 @@ export function setNexradArchiveBundleId(bundleId: string) {
57
59
  NEXRAD_ARCHIVE_BUNDLE_ID = bundleId || '';
58
60
  }
59
61
 
62
+ export function setNexradArchiveSiteOrigin(origin: string | null | undefined) {
63
+ const raw = typeof origin === 'string' ? origin.trim() : '';
64
+ NEXRAD_ARCHIVE_SITE_ORIGIN = raw ? raw.replace(/\/+$/, '') : '';
65
+ }
66
+
60
67
  /** Match {@link AguaceroCore} `urlWithApiKeyParam` for the same CloudFront distribution. */
61
68
  function cloudFrontUrlWithApiKeyQuery(baseUrl: string): string {
62
69
  if (!NEXRAD_ARCHIVE_API_KEY) return baseUrl;
@@ -76,6 +83,10 @@ function level2CloudFrontFetchHeaders(range: string | undefined): Record<string,
76
83
  if (g.navigator?.product === 'ReactNative' && NEXRAD_ARCHIVE_BUNDLE_ID) {
77
84
  headers['x-app-identifier'] = NEXRAD_ARCHIVE_BUNDLE_ID;
78
85
  }
86
+ if (NEXRAD_ARCHIVE_SITE_ORIGIN) {
87
+ headers['Origin'] = NEXRAD_ARCHIVE_SITE_ORIGIN;
88
+ headers['Referer'] = `${NEXRAD_ARCHIVE_SITE_ORIGIN}/`;
89
+ }
79
90
  return headers;
80
91
  }
81
92
 
@@ -1,25 +1,25 @@
1
- import { decodeRadarSlotMessage, type DecodeSlotRequest } from './radarDecodeSlot.js';
2
-
3
- self.onmessage = (event: MessageEvent<DecodeSlotRequest>) => {
4
- const data = event.data;
5
- if (!data || data.type !== 'DECODE_SLOT') return;
6
-
7
- try {
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
- ]);
14
- } else {
15
- self.postMessage(response);
16
- }
17
- } catch (error) {
18
- self.postMessage({
19
- type: 'DECODE_RESULT',
20
- requestId: data.requestId,
21
- gateData: null,
22
- error: error instanceof Error ? error.message : 'Unknown worker error',
23
- });
24
- }
25
- };
1
+ import { decodeRadarSlotMessage, type DecodeSlotRequest } from './radarDecodeSlot.js';
2
+
3
+ self.onmessage = (event: MessageEvent<DecodeSlotRequest>) => {
4
+ const data = event.data;
5
+ if (!data || data.type !== 'DECODE_SLOT') return;
6
+
7
+ try {
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
+ ]);
14
+ } else {
15
+ self.postMessage(response);
16
+ }
17
+ } catch (error) {
18
+ self.postMessage({
19
+ type: 'DECODE_RESULT',
20
+ requestId: data.requestId,
21
+ gateData: null,
22
+ error: error instanceof Error ? error.message : 'Unknown worker error',
23
+ });
24
+ }
25
+ };
@@ -1,195 +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
- }
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
+ }