@aguacerowx/mapsgl 0.0.54 → 0.0.55
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/package.json +3 -2
- package/src/NexradWeatherController.js +11 -1
- package/src/NwsWatchesWarningsOverlay.js +12 -8
- 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/nwsSdkConstants.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aguacerowx/mapsgl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"prepublishOnly": "npm run bundle-nexrad",
|
|
12
12
|
"bundle-nexrad": "esbuild src/nexrad/radarDecode.worker.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/radarDecode.worker.bundled.js && esbuild src/nexrad/radarArchiveCore.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/radarArchiveCore.bundled.js --external:@aguacerowx/javascript-sdk && esbuild src/nexrad/MapboxRadarLayer.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/MapboxRadarLayer.bundled.js --external:mapbox-gl --external:@aguacerowx/javascript-sdk && esbuild src/nexrad/nexradCrossSectionSampleAtLatLon.ts --format=esm --platform=browser --outfile=src/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js && esbuild src/nexrad/radarFrameGpuMatch.ts --format=esm --platform=browser --outfile=src/nexrad/radarFrameGpuMatch.bundled.js && esbuild src/nexrad/nexradMapboxFrameOpts.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/nexradMapboxFrameOpts.bundled.js --external:@aguacerowx/javascript-sdk",
|
|
13
|
+
"bundle-nexrad-rn": "node scripts/bundle-nexrad-rn.mjs",
|
|
13
14
|
"gen:nws-key": "esbuild ../../../aguacero-frontend/src/components/WarningsMenu/nwsWarningCustomizationKey.ts --bundle --format=esm --platform=neutral --outfile=src/nwsWarningCustomizationKey.gen.js"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"esbuild": "^0.21.5"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@aguacerowx/javascript-sdk": "^0.0.
|
|
25
|
+
"@aguacerowx/javascript-sdk": "^0.0.25",
|
|
25
26
|
"buffer": "^6.0.3",
|
|
26
27
|
"fzstd": "^0.1.1",
|
|
27
28
|
"mapbox-gl": "^3.4.0",
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* Preloads all timestamps on the active timeline (MRMS/satellite-style) so scrubbing does not trigger loads.
|
|
4
4
|
*/
|
|
5
5
|
import { getUnitConversionFunction, getDefaultRadarTilt } from '@aguacerowx/javascript-sdk';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
fetchAndParseArchive,
|
|
8
|
+
objectKeyToUrl,
|
|
9
|
+
setNexradArchiveApiKey,
|
|
10
|
+
setNexradArchiveBundleId,
|
|
11
|
+
setNexradSitesFetchAuth,
|
|
12
|
+
} from './nexrad/radarArchiveCore.bundled.js';
|
|
7
13
|
import { MapboxRadarLayer } from './nexrad/MapboxRadarLayer.bundled.js';
|
|
8
14
|
import { nexradBinGroupIdForKey, variableToNexradGroup } from '@aguacerowx/javascript-sdk';
|
|
9
15
|
import { sampleNexradFrameAtLatLon } from './nexrad/nexradCrossSectionSampleAtLatLon.bundled.js';
|
|
@@ -268,6 +274,8 @@ export class NexradWeatherController {
|
|
|
268
274
|
this._frameCache.clear();
|
|
269
275
|
|
|
270
276
|
setNexradArchiveApiKey(this.core.apiKey || '');
|
|
277
|
+
setNexradArchiveBundleId(this.core.bundleId || '');
|
|
278
|
+
setNexradSitesFetchAuth(this.core.apiKey || '', this.core.bundleId || '');
|
|
271
279
|
|
|
272
280
|
const times = [...(state.availableNexradTimestamps || [])]
|
|
273
281
|
.map(Number)
|
|
@@ -353,6 +361,8 @@ export class NexradWeatherController {
|
|
|
353
361
|
}
|
|
354
362
|
|
|
355
363
|
setNexradArchiveApiKey(this.core.apiKey || '');
|
|
364
|
+
setNexradArchiveBundleId(this.core.bundleId || '');
|
|
365
|
+
setNexradSitesFetchAuth(this.core.apiKey || '', this.core.bundleId || '');
|
|
356
366
|
|
|
357
367
|
const unix = Number(state.nexradTimestamp);
|
|
358
368
|
const p = this._buildFetchParamsForUnix(state, unix);
|
|
@@ -842,14 +842,6 @@ export class NwsWatchesWarningsOverlay {
|
|
|
842
842
|
|
|
843
843
|
async _initialLoad() {
|
|
844
844
|
if (this._destroyed || !this.options.enabled) return;
|
|
845
|
-
try {
|
|
846
|
-
const baseline = await this._fetchBaseline();
|
|
847
|
-
if (baseline) {
|
|
848
|
-
this._workingFc = baseline;
|
|
849
|
-
}
|
|
850
|
-
} catch (err) {
|
|
851
|
-
console.warn('[NwsWatchesWarningsOverlay] Baseline fetch failed:', err);
|
|
852
|
-
}
|
|
853
845
|
|
|
854
846
|
const paintWhenReady = () => {
|
|
855
847
|
this._ensureLayers();
|
|
@@ -862,6 +854,18 @@ export class NwsWatchesWarningsOverlay {
|
|
|
862
854
|
paintWhenReady();
|
|
863
855
|
}
|
|
864
856
|
|
|
857
|
+
void this._fetchBaseline()
|
|
858
|
+
.then((baseline) => {
|
|
859
|
+
if (this._destroyed || !this.options.enabled) return;
|
|
860
|
+
if (baseline) {
|
|
861
|
+
this._workingFc = baseline;
|
|
862
|
+
}
|
|
863
|
+
this._setWorkingData(this._workingFc);
|
|
864
|
+
})
|
|
865
|
+
.catch((err) => {
|
|
866
|
+
console.warn('[NwsWatchesWarningsOverlay] Baseline fetch failed:', err);
|
|
867
|
+
});
|
|
868
|
+
|
|
865
869
|
this._connectSse();
|
|
866
870
|
}
|
|
867
871
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { NexradSite } from './PreprocessedSweepParser.js';
|
|
2
|
+
import nexradSitesDefault from './nexradSitesDefault.json';
|
|
3
|
+
import { nexradArchiveDiag, redactApiKeyFromUrl } from './nexradArchiveDiag.js';
|
|
2
4
|
|
|
3
5
|
type NexradSitesPayload = {
|
|
4
6
|
sites?: NexradSite[];
|
|
@@ -13,22 +15,98 @@ export function setNexradSitesJsonUrl(url: string) {
|
|
|
13
15
|
sitesUrl = url || sitesUrl;
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* When sitesUrl is HTTPS (e.g. CloudFront), use the same auth as AguaceroCore grid fetches:
|
|
20
|
+
* `?apiKey=`, `x-api-key`, and `x-app-identifier` on React Native when bundle id is set.
|
|
21
|
+
*/
|
|
22
|
+
let SITES_FETCH_API_KEY = '';
|
|
23
|
+
let SITES_FETCH_BUNDLE_ID = '';
|
|
24
|
+
export function setNexradSitesFetchAuth(apiKey: string, bundleId?: string) {
|
|
25
|
+
SITES_FETCH_API_KEY = apiKey || '';
|
|
26
|
+
if (bundleId !== undefined) {
|
|
27
|
+
SITES_FETCH_BUNDLE_ID = bundleId || '';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
let nexradSitesPayloadPromise: Promise<NexradSitesPayload> | null = null;
|
|
17
32
|
|
|
33
|
+
function sitesFetchUrl(): string {
|
|
34
|
+
if (!sitesUrl.startsWith('http') || !SITES_FETCH_API_KEY) {
|
|
35
|
+
return sitesUrl;
|
|
36
|
+
}
|
|
37
|
+
const sep = sitesUrl.includes('?') ? '&' : '?';
|
|
38
|
+
return `${sitesUrl}${sep}apiKey=${SITES_FETCH_API_KEY}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sitesFetchHeaders(): Record<string, string> | undefined {
|
|
42
|
+
if (!SITES_FETCH_API_KEY) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const headers: Record<string, string> = {
|
|
46
|
+
'x-api-key': SITES_FETCH_API_KEY,
|
|
47
|
+
};
|
|
48
|
+
const nav = (globalThis as { navigator?: { product?: string } }).navigator;
|
|
49
|
+
if (nav?.product === 'ReactNative' && SITES_FETCH_BUNDLE_ID) {
|
|
50
|
+
headers['x-app-identifier'] = SITES_FETCH_BUNDLE_ID;
|
|
51
|
+
}
|
|
52
|
+
return headers;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function embeddedSitesPayload(): NexradSitesPayload {
|
|
56
|
+
return nexradSitesDefault as NexradSitesPayload;
|
|
57
|
+
}
|
|
58
|
+
|
|
18
59
|
export function loadNexradSitesPayload(): Promise<NexradSitesPayload> {
|
|
19
60
|
if (nexradSitesPayloadPromise) {
|
|
20
61
|
return nexradSitesPayloadPromise;
|
|
21
62
|
}
|
|
22
|
-
nexradSitesPayloadPromise =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
63
|
+
nexradSitesPayloadPromise = (async (): Promise<NexradSitesPayload> => {
|
|
64
|
+
const url = sitesFetchUrl();
|
|
65
|
+
const headers = sitesFetchHeaders();
|
|
66
|
+
const headerKeys = headers ? Object.keys(headers).join(',') : '';
|
|
67
|
+
nexradArchiveDiag('sites.fetch.start', {
|
|
68
|
+
canonicalUrl: sitesUrl,
|
|
69
|
+
fetchUrl: redactApiKeyFromUrl(url),
|
|
70
|
+
hasApiKeyQuery: url.includes('apiKey='),
|
|
71
|
+
headerKeys: headerKeys || '(none)',
|
|
72
|
+
apiKeyLen: SITES_FETCH_API_KEY.length,
|
|
73
|
+
bundleIdLen: SITES_FETCH_BUNDLE_ID.length,
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(url, headers ? { headers } : undefined);
|
|
77
|
+
if (response.ok) {
|
|
78
|
+
nexradArchiveDiag('sites.fetch.ok', {
|
|
79
|
+
status: response.status,
|
|
80
|
+
canonicalUrl: sitesUrl,
|
|
81
|
+
});
|
|
82
|
+
return (await response.json()) as NexradSitesPayload;
|
|
83
|
+
}
|
|
84
|
+
if (sitesUrl.startsWith('https://')) {
|
|
85
|
+
console.warn(
|
|
86
|
+
`[mapsgl] nexrad.json HTTP ${response.status} for ${sitesUrl} — using embedded site list`,
|
|
87
|
+
);
|
|
88
|
+
nexradArchiveDiag('sites.fetch.fallbackEmbedded', {
|
|
89
|
+
httpStatus: response.status,
|
|
90
|
+
reason: 'HTTPS non-OK — CloudFront may not host /data/nexrad.json; embedded list used',
|
|
91
|
+
canonicalUrl: sitesUrl,
|
|
92
|
+
});
|
|
93
|
+
return embeddedSitesPayload();
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`nexrad.json fetch failed: HTTP ${response.status}`);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (sitesUrl.startsWith('https://')) {
|
|
98
|
+
console.warn('[mapsgl] nexrad.json load failed — using embedded site list:', error);
|
|
99
|
+
nexradArchiveDiag('sites.fetch.catchFallbackEmbedded', {
|
|
100
|
+
message: error instanceof Error ? error.message : String(error),
|
|
101
|
+
canonicalUrl: sitesUrl,
|
|
102
|
+
});
|
|
103
|
+
return embeddedSitesPayload();
|
|
104
|
+
}
|
|
28
105
|
console.error('[mapsgl] Could not load nexrad.json:', error);
|
|
29
106
|
nexradSitesPayloadPromise = null;
|
|
30
107
|
throw error;
|
|
31
|
-
}
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
32
110
|
return nexradSitesPayloadPromise;
|
|
33
111
|
}
|
|
34
112
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verbose NEXRAD archive pipeline logs for React Native (Hermes / Logcat).
|
|
3
|
+
* Filter: {@code [Aguacero][NEXRAD][archive]}
|
|
4
|
+
*
|
|
5
|
+
* Omits raw API keys; use lengths / booleans only.
|
|
6
|
+
*/
|
|
7
|
+
export type NexradArchiveDiagDetail = Record<string, string | number | boolean | null | undefined>;
|
|
8
|
+
|
|
9
|
+
export function redactApiKeyFromUrl(u: string): string {
|
|
10
|
+
return u.replace(/([?&])apiKey=[^&]*/gi, '$1apiKey=(redacted)');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isReactNative(): boolean {
|
|
14
|
+
const nav = (globalThis as { navigator?: { product?: string } }).navigator;
|
|
15
|
+
return nav?.product === 'ReactNative';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Logs only on React Native to avoid noisy web consoles (mapsgl path is already observable in DevTools). */
|
|
19
|
+
export function nexradArchiveDiag(phase: string, detail?: NexradArchiveDiagDetail): void {
|
|
20
|
+
if (!isReactNative()) return;
|
|
21
|
+
if (detail !== undefined) {
|
|
22
|
+
console.warn(`[Aguacero][NEXRAD][archive] ${phase}`, detail);
|
|
23
|
+
} else {
|
|
24
|
+
console.warn(`[Aguacero][NEXRAD][archive] ${phase}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -221,7 +221,8 @@ function nexradLevel3S3ProductForSiteTilt(siteId, radarVariable, tilt) {
|
|
|
221
221
|
return products[idx];
|
|
222
222
|
}
|
|
223
223
|
function mapboxFrameUploadOptionsForNexradState(state) {
|
|
224
|
-
if (!state
|
|
224
|
+
if (!state) return { geometryLayoutKey: "canonical" };
|
|
225
|
+
if (state.nexradDataSource !== "level3") return { geometryLayoutKey: "canonical" };
|
|
225
226
|
const site = state.nexradSite;
|
|
226
227
|
const radarVar = (state.nexradProduct || "REF").toUpperCase();
|
|
227
228
|
if (nexradLevel3UsesTiltIndexedS3Products(radarVar) && site) {
|
|
@@ -235,7 +236,7 @@ function mapboxFrameUploadOptionsForNexradState(state) {
|
|
|
235
236
|
const mnemonic = nexradLevel3S3VelocityProductForSiteTilt(site, tilt);
|
|
236
237
|
return { geometryLayoutKey: `${site}|${radarVar}|${mnemonic}` };
|
|
237
238
|
}
|
|
238
|
-
return {
|
|
239
|
+
return { geometryLayoutKey: "canonical" };
|
|
239
240
|
}
|
|
240
241
|
export {
|
|
241
242
|
mapboxFrameUploadOptionsForNexradState,
|
|
@@ -104,7 +104,8 @@ type NexradStateForMapbox = {
|
|
|
104
104
|
export function mapboxFrameUploadOptionsForNexradState(
|
|
105
105
|
state: NexradStateForMapbox | null | undefined,
|
|
106
106
|
): MapboxRadarFrameUploadOptions | undefined {
|
|
107
|
-
if (!state
|
|
107
|
+
if (!state) return { geometryLayoutKey: 'canonical' };
|
|
108
|
+
if (state.nexradDataSource !== 'level3') return { geometryLayoutKey: 'canonical' };
|
|
108
109
|
const site = state.nexradSite;
|
|
109
110
|
const radarVar = (state.nexradProduct || 'REF').toUpperCase();
|
|
110
111
|
|
|
@@ -121,5 +122,5 @@ export function mapboxFrameUploadOptionsForNexradState(
|
|
|
121
122
|
return { geometryLayoutKey: `${site}|${radarVar}|${mnemonic}` };
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
return {
|
|
125
|
+
return { geometryLayoutKey: 'canonical' };
|
|
125
126
|
}
|