@aguacerowx/javascript-sdk 0.0.21 → 0.0.22
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 +6 -1
- package/src/AguaceroCore.js +38 -69
- package/src/index.js +1 -0
- package/src/nexradTiltCoalesce.js +95 -0
- package/src/nexrad_support.js +21 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aguacerowx/javascript-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"import": "./src/nexrad_support.js",
|
|
26
26
|
"require": "./src/nexrad_support.js",
|
|
27
27
|
"default": "./src/nexrad_support.js"
|
|
28
|
+
},
|
|
29
|
+
"./nexradTiltCoalesce.js": {
|
|
30
|
+
"import": "./src/nexradTiltCoalesce.js",
|
|
31
|
+
"require": "./src/nexradTiltCoalesce.js",
|
|
32
|
+
"default": "./src/nexradTiltCoalesce.js"
|
|
28
33
|
}
|
|
29
34
|
},
|
|
30
35
|
"files": [
|
package/src/AguaceroCore.js
CHANGED
|
@@ -23,7 +23,9 @@ import {
|
|
|
23
23
|
nexradColormapFldKey,
|
|
24
24
|
variableToNexradGroup,
|
|
25
25
|
getAvailableNexradTilts,
|
|
26
|
+
getRawNexradTiltsForCoalesce,
|
|
26
27
|
} from './nexrad_support.js';
|
|
28
|
+
import { nexradLayerTiltToDisplayOption } from './nexradTiltCoalesce.js';
|
|
27
29
|
import { NEXRAD_LEVEL3_ELEV, getNexradLevel3EntryByRadarKey } from './nexrad_level3_catalog.js';
|
|
28
30
|
import {
|
|
29
31
|
setRadarTiltsManifest,
|
|
@@ -142,12 +144,7 @@ export class AguaceroCore extends EventEmitter {
|
|
|
142
144
|
const initialSatellite = initialMode === 'satellite';
|
|
143
145
|
const initialNexrad = initialMode === 'nexrad';
|
|
144
146
|
const initialNexradProd = userLayerOptions.nexradProduct || 'REF';
|
|
145
|
-
const initialNexradDs =
|
|
146
|
-
userLayerOptions.nexradDataSource != null
|
|
147
|
-
? userLayerOptions.nexradDataSource === 'level3'
|
|
148
|
-
? 'level3'
|
|
149
|
-
: 'level2'
|
|
150
|
-
: inferNexradDataSourceForProduct(initialNexradProd);
|
|
147
|
+
const initialNexradDs = inferNexradDataSourceForProduct(initialNexradProd);
|
|
151
148
|
const initialNexradFld = initialNexrad
|
|
152
149
|
? nexradColormapFldKey(initialNexradDs, initialNexradProd)
|
|
153
150
|
: initialVariable;
|
|
@@ -224,6 +221,19 @@ export class AguaceroCore extends EventEmitter {
|
|
|
224
221
|
async setState(newState) {
|
|
225
222
|
const patch = { ...newState };
|
|
226
223
|
if ('satelliteKey' in patch) delete patch.satelliteKey;
|
|
224
|
+
if ('nexradDataSource' in patch && !('nexradProduct' in patch)) {
|
|
225
|
+
delete patch.nexradDataSource;
|
|
226
|
+
}
|
|
227
|
+
const willBeNexrad =
|
|
228
|
+
patch.isNexrad !== undefined ? Boolean(patch.isNexrad) : this.state.isNexrad;
|
|
229
|
+
if (willBeNexrad && 'nexradProduct' in patch && patch.nexradProduct != null) {
|
|
230
|
+
const p = (patch.nexradProduct || 'REF').toUpperCase();
|
|
231
|
+
const ds = inferNexradDataSourceForProduct(p);
|
|
232
|
+
patch.nexradProduct = p;
|
|
233
|
+
patch.nexradDataSource = ds;
|
|
234
|
+
patch.variable = nexradColormapFldKey(ds, p);
|
|
235
|
+
patch.nexradStormRelative = this._nexradStormRelativeFor(ds, p);
|
|
236
|
+
}
|
|
227
237
|
if ('forecastHour' in patch && patch.forecastHour != null) {
|
|
228
238
|
patch.forecastHour = Number(patch.forecastHour);
|
|
229
239
|
}
|
|
@@ -922,13 +932,7 @@ export class AguaceroCore extends EventEmitter {
|
|
|
922
932
|
};
|
|
923
933
|
} else if (mode === 'nexrad') {
|
|
924
934
|
const nexradProduct = options.nexradProduct || 'REF';
|
|
925
|
-
const
|
|
926
|
-
options.nexradDataSource != null
|
|
927
|
-
? options.nexradDataSource === 'level3'
|
|
928
|
-
? 'level3'
|
|
929
|
-
: 'level2'
|
|
930
|
-
: inferNexradDataSourceForProduct(nexradProduct);
|
|
931
|
-
const fld = nexradColormapFldKey(nexradDataSource, nexradProduct);
|
|
935
|
+
const p = nexradProduct.toUpperCase();
|
|
932
936
|
const site = options.nexradSite ?? null;
|
|
933
937
|
let tilt =
|
|
934
938
|
options.nexradTilt != null
|
|
@@ -940,10 +944,8 @@ export class AguaceroCore extends EventEmitter {
|
|
|
940
944
|
isNexrad: true,
|
|
941
945
|
isMRMS: false,
|
|
942
946
|
isSatellite: false,
|
|
943
|
-
variable: fld,
|
|
944
947
|
nexradSite: site,
|
|
945
|
-
|
|
946
|
-
nexradProduct,
|
|
948
|
+
nexradProduct: p,
|
|
947
949
|
nexradTilt: tilt,
|
|
948
950
|
nexradTimestamp: options.nexradTimestamp != null ? Number(options.nexradTimestamp) : null,
|
|
949
951
|
nexradStormRelative: options.nexradStormRelative === true,
|
|
@@ -985,26 +987,23 @@ export class AguaceroCore extends EventEmitter {
|
|
|
985
987
|
}
|
|
986
988
|
|
|
987
989
|
/**
|
|
988
|
-
* Keep `nexradTilt` on
|
|
990
|
+
* Keep `nexradTilt` on a coalesced elevation (same rules as aguacero-frontend tilt controls).
|
|
989
991
|
*/
|
|
990
992
|
async _snapNexradTiltToAvailableOptions() {
|
|
991
993
|
const s = this.state;
|
|
992
994
|
if (!s.isNexrad || !s.nexradSite) return;
|
|
993
|
-
const
|
|
995
|
+
const raw = getRawNexradTiltsForCoalesce(
|
|
994
996
|
s.nexradSite,
|
|
995
997
|
s.nexradDataSource || 'level2',
|
|
996
998
|
s.nexradProduct || 'REF',
|
|
997
999
|
);
|
|
998
|
-
if (!
|
|
1000
|
+
if (!raw.length) return;
|
|
999
1001
|
const t = s.nexradTilt;
|
|
1000
|
-
const match = (a, b) => Math.abs(Number(a) - Number(b)) < 1e-4;
|
|
1001
|
-
if (t != null && tilts.some((x) => match(x, t))) return;
|
|
1002
1002
|
const target = t != null && Number.isFinite(Number(t)) ? Number(t) : getDefaultRadarTilt(s.nexradSite);
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
await this.setState({ nexradTilt: best });
|
|
1003
|
+
const canonical = nexradLayerTiltToDisplayOption(target, raw);
|
|
1004
|
+
const match = (a, b) => Math.abs(Number(a) - Number(b)) < 1e-4;
|
|
1005
|
+
if (match(s.nexradTilt, canonical)) return;
|
|
1006
|
+
await this.setState({ nexradTilt: canonical });
|
|
1008
1007
|
}
|
|
1009
1008
|
|
|
1010
1009
|
async refreshNexradTimes() {
|
|
@@ -1044,6 +1043,18 @@ export class AguaceroCore extends EventEmitter {
|
|
|
1044
1043
|
listWindowHours: listingHours,
|
|
1045
1044
|
};
|
|
1046
1045
|
}
|
|
1046
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(out.unixTimes || []);
|
|
1047
|
+
const map = out.timeToKeyMap || {};
|
|
1048
|
+
if (filtered.length > 0) {
|
|
1049
|
+
const cur = this.state.nexradTimestamp != null ? Number(this.state.nexradTimestamp) : null;
|
|
1050
|
+
const hasKey = cur != null && Object.prototype.hasOwnProperty.call(map, String(cur));
|
|
1051
|
+
const inWindow = cur != null && filtered.includes(cur);
|
|
1052
|
+
if (cur == null || !inWindow || !hasKey) {
|
|
1053
|
+
this.state.nexradTimestamp = filtered[filtered.length - 1];
|
|
1054
|
+
}
|
|
1055
|
+
} else if (this.state.nexradTimestamp != null) {
|
|
1056
|
+
this.state.nexradTimestamp = null;
|
|
1057
|
+
}
|
|
1047
1058
|
this._emitStateChange();
|
|
1048
1059
|
}
|
|
1049
1060
|
|
|
@@ -1067,49 +1078,7 @@ export class AguaceroCore extends EventEmitter {
|
|
|
1067
1078
|
async setNexradProduct(product) {
|
|
1068
1079
|
if (!this.state.isNexrad) return;
|
|
1069
1080
|
const p = (product || 'REF').toUpperCase();
|
|
1070
|
-
|
|
1071
|
-
const fld = nexradColormapFldKey(ds, p);
|
|
1072
|
-
const nexradStormRelative = this._nexradStormRelativeFor(ds, p);
|
|
1073
|
-
await this.setState({ nexradProduct: p, nexradDataSource: ds, variable: fld, nexradStormRelative });
|
|
1074
|
-
await this.refreshNexradTimes();
|
|
1075
|
-
const filtered = this._getFilteredNexradTimestampsForVariable(
|
|
1076
|
-
this.nexradTimesByStation[this._nexradTimesCacheKey()]?.unixTimes || [],
|
|
1077
|
-
);
|
|
1078
|
-
if (filtered.length > 0) {
|
|
1079
|
-
await this.setState({ nexradTimestamp: filtered[filtered.length - 1] });
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
async setNexradDataSource(source) {
|
|
1084
|
-
if (!this.state.isNexrad) return;
|
|
1085
|
-
const ds = source === 'level3' ? 'level3' : 'level2';
|
|
1086
|
-
const p = (this.state.nexradProduct || 'REF').toUpperCase();
|
|
1087
|
-
const fld = nexradColormapFldKey(ds, p);
|
|
1088
|
-
const nexradStormRelative = this._nexradStormRelativeFor(ds, p);
|
|
1089
|
-
await this.setState({ nexradDataSource: ds, variable: fld, nexradStormRelative });
|
|
1090
|
-
await this.refreshNexradTimes();
|
|
1091
|
-
const filtered = this._getFilteredNexradTimestampsForVariable(
|
|
1092
|
-
this.nexradTimesByStation[this._nexradTimesCacheKey()]?.unixTimes || [],
|
|
1093
|
-
);
|
|
1094
|
-
if (filtered.length > 0) {
|
|
1095
|
-
await this.setState({ nexradTimestamp: filtered[filtered.length - 1] });
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* Sets NEXRAD product and archive source together (single refresh). Prefer {@link setNexradProduct}
|
|
1101
|
-
* for product-only changes (Level II vs III is inferred). Use this when you must force a specific
|
|
1102
|
-
* archive source (rare).
|
|
1103
|
-
* @param {'level2'|'level3'} dataSource
|
|
1104
|
-
* @param {string} product - Level-II variable (REF, VEL, …) or Level-III radar key (N0H, HHC, …)
|
|
1105
|
-
*/
|
|
1106
|
-
async setNexradProductMode(dataSource, product) {
|
|
1107
|
-
if (!this.state.isNexrad) return;
|
|
1108
|
-
const ds = dataSource === 'level3' ? 'level3' : 'level2';
|
|
1109
|
-
const p = (product || 'REF').toUpperCase();
|
|
1110
|
-
const fld = nexradColormapFldKey(ds, p);
|
|
1111
|
-
const nexradStormRelative = this._nexradStormRelativeFor(ds, p);
|
|
1112
|
-
await this.setState({ nexradDataSource: ds, nexradProduct: p, variable: fld, nexradStormRelative });
|
|
1081
|
+
await this.setState({ nexradProduct: p });
|
|
1113
1082
|
await this.refreshNexradTimes();
|
|
1114
1083
|
const filtered = this._getFilteredNexradTimestampsForVariable(
|
|
1115
1084
|
this.nexradTimesByStation[this._nexradTimesCacheKey()]?.unixTimes || [],
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NEXRAD tilt coalescing (matches aguacero-frontend `nexradTiltCoalesce.ts`).
|
|
3
|
+
* Adjacent manifest tilts within {@link NEXRAD_TILT_COALESCE_MAX_GAP}° are merged for UI/listings:
|
|
4
|
+
* pairs → higher tilt; runs of three or more consecutive steps merge the first two into the lower tilt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Adjacent tilts (sorted) within this gap share one listing / control. */
|
|
8
|
+
export const NEXRAD_TILT_COALESCE_MAX_GAP = 0.1;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {number[]} segment
|
|
12
|
+
* @param {Map<number, number>} map
|
|
13
|
+
*/
|
|
14
|
+
function mergeCoalesceSegment(segment, map) {
|
|
15
|
+
if (segment.length === 0) return;
|
|
16
|
+
if (segment.length === 1) {
|
|
17
|
+
map.set(segment[0], segment[0]);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (segment.length === 2) {
|
|
21
|
+
const canonical = Math.max(segment[0], segment[1]);
|
|
22
|
+
map.set(segment[0], canonical);
|
|
23
|
+
map.set(segment[1], canonical);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const low = segment[0];
|
|
27
|
+
map.set(segment[0], low);
|
|
28
|
+
map.set(segment[1], low);
|
|
29
|
+
mergeCoalesceSegment(segment.slice(2), map);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number[]} sortedUnique
|
|
34
|
+
* @returns {Map<number, number>}
|
|
35
|
+
*/
|
|
36
|
+
export function chainCoalesceToCanonical(sortedUnique) {
|
|
37
|
+
const map = new Map();
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < sortedUnique.length) {
|
|
40
|
+
let j = i + 1;
|
|
41
|
+
while (
|
|
42
|
+
j < sortedUnique.length &&
|
|
43
|
+
sortedUnique[j] - sortedUnique[j - 1] <= NEXRAD_TILT_COALESCE_MAX_GAP + 1e-9
|
|
44
|
+
) {
|
|
45
|
+
j++;
|
|
46
|
+
}
|
|
47
|
+
const segment = sortedUnique.slice(i, j);
|
|
48
|
+
mergeCoalesceSegment(segment, map);
|
|
49
|
+
i = j;
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Manifest tilt list with adjacent options merged (pairs → higher; triple+ splits per merge rules).
|
|
56
|
+
* @param {number[]} tilts
|
|
57
|
+
* @returns {number[]}
|
|
58
|
+
*/
|
|
59
|
+
export function coalesceNexradTiltOptionsForDisplay(tilts) {
|
|
60
|
+
const sorted = [...new Set(tilts)]
|
|
61
|
+
.filter((t) => Number.isFinite(t))
|
|
62
|
+
.sort((a, b) => a - b);
|
|
63
|
+
if (sorted.length <= 1) return sorted;
|
|
64
|
+
const tiltToCanonical = chainCoalesceToCanonical(sorted);
|
|
65
|
+
const reps = [];
|
|
66
|
+
let prevRep;
|
|
67
|
+
for (const t of sorted) {
|
|
68
|
+
const c = tiltToCanonical.get(t);
|
|
69
|
+
if (c === undefined) continue;
|
|
70
|
+
if (c !== prevRep) {
|
|
71
|
+
reps.push(c);
|
|
72
|
+
prevRep = c;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return reps;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Map stored/clamped tilt to the coalesced elevation value (same rules as {@link coalesceNexradTiltOptionsForDisplay}).
|
|
80
|
+
* @param {number} layerTilt
|
|
81
|
+
* @param {number[]} manifestTilts raw manifest tilts (before coalescing)
|
|
82
|
+
* @returns {number}
|
|
83
|
+
*/
|
|
84
|
+
export function nexradLayerTiltToDisplayOption(layerTilt, manifestTilts) {
|
|
85
|
+
const sorted = [...new Set(manifestTilts)]
|
|
86
|
+
.filter((t) => Number.isFinite(t))
|
|
87
|
+
.sort((a, b) => a - b);
|
|
88
|
+
if (sorted.length === 0) return layerTilt;
|
|
89
|
+
const tiltToCanonical = chainCoalesceToCanonical(sorted);
|
|
90
|
+
if (tiltToCanonical.has(layerTilt)) return tiltToCanonical.get(layerTilt);
|
|
91
|
+
const nearest = sorted.reduce((best, x) =>
|
|
92
|
+
Math.abs(x - layerTilt) < Math.abs(best - layerTilt) ? x : best,
|
|
93
|
+
sorted[0]);
|
|
94
|
+
return tiltToCanonical.get(nearest) ?? layerTilt;
|
|
95
|
+
}
|
package/src/nexrad_support.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getDefaultRadarTilt,
|
|
9
9
|
getRadarTilts,
|
|
10
10
|
} from './nexradTilts.js';
|
|
11
|
+
import { coalesceNexradTiltOptionsForDisplay } from './nexradTiltCoalesce.js';
|
|
11
12
|
import {
|
|
12
13
|
NEXRAD_LEVEL3_ELEV,
|
|
13
14
|
NEXRAD_LEVEL3_MOTION_PRODUCT,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Whether listings and archive fetches for this product use Level-II sweep lambda vs Level-III S3 products.
|
|
19
|
-
* AguaceroCore
|
|
20
|
+
* AguaceroCore derives this from the product key only (not user-configurable).
|
|
20
21
|
* @param {string} [nexradProduct] - Radar variable key (e.g. REF, KDP, VEL).
|
|
21
22
|
* @returns {'level2'|'level3'}
|
|
22
23
|
*/
|
|
@@ -263,13 +264,15 @@ export async function fetchNexradTimesListing(opts) {
|
|
|
263
264
|
const L3_TILT_INDEX_MANIFEST_SLOTS = 6;
|
|
264
265
|
|
|
265
266
|
/**
|
|
266
|
-
*
|
|
267
|
+
* Raw manifest tilt list before coalescing (same rules as aguacero-frontend `radarTiltOptionsRaw`).
|
|
268
|
+
* Use {@link coalesceNexradTiltOptionsForDisplay} or {@link getAvailableNexradTilts} for UI controls.
|
|
269
|
+
*
|
|
267
270
|
* @param {string} siteId
|
|
268
271
|
* @param {'level2'|'level3'} nexradDataSource
|
|
269
272
|
* @param {string} [nexradProduct]
|
|
270
273
|
* @returns {number[]}
|
|
271
274
|
*/
|
|
272
|
-
export function
|
|
275
|
+
export function getRawNexradTiltsForCoalesce(siteId, nexradDataSource, nexradProduct) {
|
|
273
276
|
if (!siteId) return [];
|
|
274
277
|
const v = (nexradProduct || 'REF').toUpperCase();
|
|
275
278
|
const ds = nexradDataSource === 'level3' ? 'level3' : 'level2';
|
|
@@ -285,3 +288,18 @@ export function getAvailableNexradTilts(siteId, nexradDataSource, nexradProduct)
|
|
|
285
288
|
}
|
|
286
289
|
return [];
|
|
287
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Tilt angles for the UI: manifest tilts with adjacent 0.1° steps merged like aguacero-frontend
|
|
294
|
+
* ({@link coalesceNexradTiltOptionsForDisplay}).
|
|
295
|
+
*
|
|
296
|
+
* @param {string} siteId
|
|
297
|
+
* @param {'level2'|'level3'} nexradDataSource
|
|
298
|
+
* @param {string} [nexradProduct]
|
|
299
|
+
* @returns {number[]}
|
|
300
|
+
*/
|
|
301
|
+
export function getAvailableNexradTilts(siteId, nexradDataSource, nexradProduct) {
|
|
302
|
+
const raw = getRawNexradTiltsForCoalesce(siteId, nexradDataSource, nexradProduct);
|
|
303
|
+
if (!raw.length) return [];
|
|
304
|
+
return coalesceNexradTiltOptionsForDisplay(raw);
|
|
305
|
+
}
|