@aguacerowx/javascript-sdk 0.0.21 → 0.0.23
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 +53 -73
- package/src/index.js +1 -0
- package/src/nexradTiltCoalesce.js +95 -0
- package/src/nexrad_support.js +25 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aguacerowx/javascript-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
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,27 @@ 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 prevP = (this.state.nexradProduct || 'REF').toUpperCase();
|
|
232
|
+
const ds = inferNexradDataSourceForProduct(p);
|
|
233
|
+
patch.nexradProduct = p;
|
|
234
|
+
patch.nexradDataSource = ds;
|
|
235
|
+
patch.variable = nexradColormapFldKey(ds, p);
|
|
236
|
+
if (!('nexradStormRelative' in newState)) {
|
|
237
|
+
// Default: base radial velocity (L3 N0G only) — one fetch per time; fast scrub.
|
|
238
|
+
// SRV (N0G + N0S) is opt-in: setNexradStormRelative(true) or pass nexradStormRelative in setState.
|
|
239
|
+
// Re-selecting the same product (e.g. VEL → VEL) leaves the current SRV flag alone.
|
|
240
|
+
if (p !== 'VEL' || prevP !== 'VEL') {
|
|
241
|
+
patch.nexradStormRelative = false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
227
245
|
if ('forecastHour' in patch && patch.forecastHour != null) {
|
|
228
246
|
patch.forecastHour = Number(patch.forecastHour);
|
|
229
247
|
}
|
|
@@ -363,13 +381,6 @@ export class AguaceroCore extends EventEmitter {
|
|
|
363
381
|
return `${site}_${group}_${elevNormUse}`;
|
|
364
382
|
}
|
|
365
383
|
|
|
366
|
-
/** Storm-relative Level-III velocity (N0G + N0S) — matches aguacero-frontend `level3StormRelative` for VEL. */
|
|
367
|
-
_nexradStormRelativeFor(nexradDataSource, nexradProduct) {
|
|
368
|
-
const ds = nexradDataSource === 'level3' ? 'level3' : 'level2';
|
|
369
|
-
const p = (nexradProduct || 'REF').toUpperCase();
|
|
370
|
-
return ds === 'level3' && p === 'VEL';
|
|
371
|
-
}
|
|
372
|
-
|
|
373
384
|
_emitStateChange() {
|
|
374
385
|
const { colormap, baseUnit } = this._getColormapForVariable(this.state.variable);
|
|
375
386
|
const toUnit = this._getTargetUnit(baseUnit, this.state.units);
|
|
@@ -922,13 +933,7 @@ export class AguaceroCore extends EventEmitter {
|
|
|
922
933
|
};
|
|
923
934
|
} else if (mode === 'nexrad') {
|
|
924
935
|
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);
|
|
936
|
+
const p = nexradProduct.toUpperCase();
|
|
932
937
|
const site = options.nexradSite ?? null;
|
|
933
938
|
let tilt =
|
|
934
939
|
options.nexradTilt != null
|
|
@@ -940,10 +945,8 @@ export class AguaceroCore extends EventEmitter {
|
|
|
940
945
|
isNexrad: true,
|
|
941
946
|
isMRMS: false,
|
|
942
947
|
isSatellite: false,
|
|
943
|
-
variable: fld,
|
|
944
948
|
nexradSite: site,
|
|
945
|
-
|
|
946
|
-
nexradProduct,
|
|
949
|
+
nexradProduct: p,
|
|
947
950
|
nexradTilt: tilt,
|
|
948
951
|
nexradTimestamp: options.nexradTimestamp != null ? Number(options.nexradTimestamp) : null,
|
|
949
952
|
nexradStormRelative: options.nexradStormRelative === true,
|
|
@@ -985,26 +988,23 @@ export class AguaceroCore extends EventEmitter {
|
|
|
985
988
|
}
|
|
986
989
|
|
|
987
990
|
/**
|
|
988
|
-
* Keep `nexradTilt` on
|
|
991
|
+
* Keep `nexradTilt` on a coalesced elevation (same rules as aguacero-frontend tilt controls).
|
|
989
992
|
*/
|
|
990
993
|
async _snapNexradTiltToAvailableOptions() {
|
|
991
994
|
const s = this.state;
|
|
992
995
|
if (!s.isNexrad || !s.nexradSite) return;
|
|
993
|
-
const
|
|
996
|
+
const raw = getRawNexradTiltsForCoalesce(
|
|
994
997
|
s.nexradSite,
|
|
995
998
|
s.nexradDataSource || 'level2',
|
|
996
999
|
s.nexradProduct || 'REF',
|
|
997
1000
|
);
|
|
998
|
-
if (!
|
|
1001
|
+
if (!raw.length) return;
|
|
999
1002
|
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
1003
|
const target = t != null && Number.isFinite(Number(t)) ? Number(t) : getDefaultRadarTilt(s.nexradSite);
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
await this.setState({ nexradTilt: best });
|
|
1004
|
+
const canonical = nexradLayerTiltToDisplayOption(target, raw);
|
|
1005
|
+
const match = (a, b) => Math.abs(Number(a) - Number(b)) < 1e-4;
|
|
1006
|
+
if (match(s.nexradTilt, canonical)) return;
|
|
1007
|
+
await this.setState({ nexradTilt: canonical });
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
1010
|
async refreshNexradTimes() {
|
|
@@ -1044,6 +1044,18 @@ export class AguaceroCore extends EventEmitter {
|
|
|
1044
1044
|
listWindowHours: listingHours,
|
|
1045
1045
|
};
|
|
1046
1046
|
}
|
|
1047
|
+
const filtered = this._getFilteredNexradTimestampsForVariable(out.unixTimes || []);
|
|
1048
|
+
const map = out.timeToKeyMap || {};
|
|
1049
|
+
if (filtered.length > 0) {
|
|
1050
|
+
const cur = this.state.nexradTimestamp != null ? Number(this.state.nexradTimestamp) : null;
|
|
1051
|
+
const hasKey = cur != null && Object.prototype.hasOwnProperty.call(map, String(cur));
|
|
1052
|
+
const inWindow = cur != null && filtered.includes(cur);
|
|
1053
|
+
if (cur == null || !inWindow || !hasKey) {
|
|
1054
|
+
this.state.nexradTimestamp = filtered[filtered.length - 1];
|
|
1055
|
+
}
|
|
1056
|
+
} else if (this.state.nexradTimestamp != null) {
|
|
1057
|
+
this.state.nexradTimestamp = null;
|
|
1058
|
+
}
|
|
1047
1059
|
this._emitStateChange();
|
|
1048
1060
|
}
|
|
1049
1061
|
|
|
@@ -1067,10 +1079,7 @@ export class AguaceroCore extends EventEmitter {
|
|
|
1067
1079
|
async setNexradProduct(product) {
|
|
1068
1080
|
if (!this.state.isNexrad) return;
|
|
1069
1081
|
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 });
|
|
1082
|
+
await this.setState({ nexradProduct: p });
|
|
1074
1083
|
await this.refreshNexradTimes();
|
|
1075
1084
|
const filtered = this._getFilteredNexradTimestampsForVariable(
|
|
1076
1085
|
this.nexradTimesByStation[this._nexradTimesCacheKey()]?.unixTimes || [],
|
|
@@ -1080,48 +1089,19 @@ export class AguaceroCore extends EventEmitter {
|
|
|
1080
1089
|
}
|
|
1081
1090
|
}
|
|
1082
1091
|
|
|
1083
|
-
async
|
|
1084
|
-
if (!this.state.isNexrad) return;
|
|
1085
|
-
|
|
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 });
|
|
1092
|
+
async setNexradTilt(tilt) {
|
|
1093
|
+
if (!this.state.isNexrad || !this.state.nexradSite) return;
|
|
1094
|
+
await this.setState({ nexradTilt: tilt != null ? Number(tilt) : null });
|
|
1090
1095
|
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
1096
|
}
|
|
1098
1097
|
|
|
1099
1098
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
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, …)
|
|
1099
|
+
* Opt-in storm-relative velocity (L3: N0G + N0S). When off (default), only base radial velocity is used (faster).
|
|
1100
|
+
* @param {boolean} enabled
|
|
1105
1101
|
*/
|
|
1106
|
-
async
|
|
1102
|
+
async setNexradStormRelative(enabled) {
|
|
1107
1103
|
if (!this.state.isNexrad) return;
|
|
1108
|
-
|
|
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 });
|
|
1113
|
-
await this.refreshNexradTimes();
|
|
1114
|
-
const filtered = this._getFilteredNexradTimestampsForVariable(
|
|
1115
|
-
this.nexradTimesByStation[this._nexradTimesCacheKey()]?.unixTimes || [],
|
|
1116
|
-
);
|
|
1117
|
-
if (filtered.length > 0) {
|
|
1118
|
-
await this.setState({ nexradTimestamp: filtered[filtered.length - 1] });
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
async setNexradTilt(tilt) {
|
|
1123
|
-
if (!this.state.isNexrad || !this.state.nexradSite) return;
|
|
1124
|
-
await this.setState({ nexradTilt: tilt != null ? Number(tilt) : null });
|
|
1104
|
+
await this.setState({ nexradStormRelative: Boolean(enabled) });
|
|
1125
1105
|
await this.refreshNexradTimes();
|
|
1126
1106
|
}
|
|
1127
1107
|
|
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,25 +264,43 @@ 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';
|
|
276
279
|
const tilts = getRadarTilts(siteId, v);
|
|
280
|
+
/** L3 super-res VEL (N*G) uses the same first-N manifest slots as KDP/N0H — not the full L2 g2-only list. */
|
|
281
|
+
if (v === 'VEL') {
|
|
282
|
+
return tilts.slice(0, L3_TILT_INDEX_MANIFEST_SLOTS);
|
|
283
|
+
}
|
|
277
284
|
if (ds === 'level2') {
|
|
278
285
|
return [...tilts];
|
|
279
286
|
}
|
|
280
287
|
if (v === 'KDP' || v === 'N0H') {
|
|
281
288
|
return tilts.slice(0, L3_TILT_INDEX_MANIFEST_SLOTS);
|
|
282
289
|
}
|
|
283
|
-
if (v === 'VEL') {
|
|
284
|
-
return [...tilts];
|
|
285
|
-
}
|
|
286
290
|
return [];
|
|
287
291
|
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Tilt angles for the UI: manifest tilts with adjacent 0.1° steps merged like aguacero-frontend
|
|
295
|
+
* ({@link coalesceNexradTiltOptionsForDisplay}).
|
|
296
|
+
*
|
|
297
|
+
* @param {string} siteId
|
|
298
|
+
* @param {'level2'|'level3'} nexradDataSource
|
|
299
|
+
* @param {string} [nexradProduct]
|
|
300
|
+
* @returns {number[]}
|
|
301
|
+
*/
|
|
302
|
+
export function getAvailableNexradTilts(siteId, nexradDataSource, nexradProduct) {
|
|
303
|
+
const raw = getRawNexradTiltsForCoalesce(siteId, nexradDataSource, nexradProduct);
|
|
304
|
+
if (!raw.length) return [];
|
|
305
|
+
return coalesceNexradTiltOptionsForDisplay(raw);
|
|
306
|
+
}
|