@aguacerowx/javascript-sdk 0.0.2 → 0.0.4

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.
@@ -117,17 +117,20 @@ class AguaceroCore extends _events.EventEmitter {
117
117
  const timestamps = this.mrmsStatus[this.state.variable] || [];
118
118
  availableTimestamps = [...timestamps].reverse();
119
119
  }
120
- this.emit('state:change', {
120
+ const eventPayload = {
121
121
  ...this.state,
122
122
  availableModels: this.modelStatus ? Object.keys(this.modelStatus).sort() : [],
123
123
  availableRuns: ((_this$modelStatus = this.modelStatus) === null || _this$modelStatus === void 0 ? void 0 : _this$modelStatus[this.state.model]) || {},
124
124
  availableHours: this.state.isMRMS ? [] : ((_this$modelStatus2 = this.modelStatus) === null || _this$modelStatus2 === void 0 || (_this$modelStatus2 = _this$modelStatus2[this.state.model]) === null || _this$modelStatus2 === void 0 || (_this$modelStatus2 = _this$modelStatus2[this.state.date]) === null || _this$modelStatus2 === void 0 ? void 0 : _this$modelStatus2[this.state.run]) || [],
125
125
  availableVariables: this.getAvailableVariables(this.state.isMRMS ? 'mrms' : this.state.model),
126
+ // We need to confirm this line is working as expected.
127
+ availableMRMSVariables: this.getAvailableVariables('mrms'),
126
128
  availableTimestamps: availableTimestamps,
127
129
  isPlaying: this.isPlaying,
128
130
  colormap: displayColormap,
129
131
  colormapBaseUnit: toUnit
130
- });
132
+ };
133
+ this.emit('state:change', eventPayload);
131
134
  }
132
135
  async initialize(options = {}) {
133
136
  await this.fetchModelStatus(true);
@@ -379,7 +382,16 @@ class AguaceroCore extends _events.EventEmitter {
379
382
  return this.dataCache.get(dataUrlIdentifier);
380
383
  }
381
384
 
382
- // Create and store abort controller BEFORE creating the promise
385
+ // --- EDITED ---
386
+ // If we are in React Native, this function should NOT do any work.
387
+ // The native WeatherFrameProcessorModule is now responsible for all data loading.
388
+ // This function might still be called by a "cache miss" fallback, but it
389
+ // should not fetch data from JS anymore. We return null so the fallback knows
390
+ // that the native module is the only source of truth for new data.
391
+ if (this.isReactNative) {
392
+ console.warn(`_loadGridData was called in React Native for ${dataUrlIdentifier}. This should be handled by the native module. Returning null.`);
393
+ return null;
394
+ }
383
395
  const abortController = new AbortController();
384
396
  this.abortControllers.set(dataUrlIdentifier, abortController);
385
397
  const loadPromise = (async () => {
@@ -388,21 +400,13 @@ class AguaceroCore extends _events.EventEmitter {
388
400
  }
389
401
  try {
390
402
  const baseUrl = `${this.baseGridUrl}${resourcePath}`;
391
-
392
- // NEW: Construct the URL with the apiKey as a query parameter
393
403
  const urlWithApiKeyParam = `${baseUrl}?apiKey=${this.apiKey}`;
394
-
395
- // --- END OF MODIFICATION ---
396
-
397
- // The headers object remains the same, sending the key in the header as well
398
404
  const headers = {
399
405
  'x-api-key': this.apiKey
400
406
  };
401
407
  if (this.bundleId) {
402
408
  headers['x-app-identifier'] = this.bundleId;
403
409
  }
404
-
405
- // Use the NEW url variable in the fetch call
406
410
  const response = await fetch(urlWithApiKeyParam, {
407
411
  headers: headers,
408
412
  signal: abortController.signal
@@ -415,33 +419,27 @@ class AguaceroCore extends _events.EventEmitter {
415
419
  encoding
416
420
  } = await response.json();
417
421
  const compressedData = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
418
- let finalData;
419
- if (this.isReactNative) {
420
- const decompressedDeltas = (0, _fzstd.decompress)(compressedData);
421
- finalData = this._reconstructData(decompressedDeltas, encoding);
422
- } else {
423
- const requestId = this.workerRequestId++;
424
- const workerPromise = new Promise((resolve, reject) => {
425
- this.workerResolvers.set(requestId, {
426
- resolve,
427
- reject
428
- });
422
+
423
+ // This path is now ONLY for the web worker
424
+ const requestId = this.workerRequestId++;
425
+ const workerPromise = new Promise((resolve, reject) => {
426
+ this.workerResolvers.set(requestId, {
427
+ resolve,
428
+ reject
429
429
  });
430
- this.worker.postMessage({
431
- requestId,
432
- compressedData,
433
- encoding
434
- }, [compressedData.buffer]);
435
- const result = await workerPromise;
436
- finalData = result.data;
437
- }
430
+ });
431
+ this.worker.postMessage({
432
+ requestId,
433
+ compressedData,
434
+ encoding
435
+ }, [compressedData.buffer]);
436
+ const result = await workerPromise;
437
+ const finalData = result.data;
438
438
  const transformedData = new Uint8Array(finalData.length);
439
439
  for (let i = 0; i < finalData.length; i++) {
440
440
  const signedValue = finalData[i] > 127 ? finalData[i] - 256 : finalData[i];
441
441
  transformedData[i] = signedValue + 128;
442
442
  }
443
-
444
- // Clean up abort controller on success
445
443
  this.abortControllers.delete(dataUrlIdentifier);
446
444
  return {
447
445
  data: transformedData,
@@ -550,6 +548,9 @@ class AguaceroCore extends _events.EventEmitter {
550
548
  colormap: [],
551
549
  baseUnit: ''
552
550
  };
551
+
552
+ // This logic for user-provided custom colormaps is correct.
553
+ // If a user provides a custom map, it should always take top priority.
553
554
  if (this.customColormaps[variable] && this.customColormaps[variable].colormap) {
554
555
  return {
555
556
  colormap: this.customColormaps[variable].colormap,
@@ -565,19 +566,45 @@ class AguaceroCore extends _events.EventEmitter {
565
566
  };
566
567
  }
567
568
  const defaultColormapData = _defaultColormaps.DEFAULT_COLORMAPS[colormapKey];
569
+
570
+ // --- START OF THE BUG FIX ---
571
+ // This block handles the "default" case when no custom colormap is provided.
568
572
  if (defaultColormapData && defaultColormapData.units) {
573
+ var _DICTIONARIES$fld$var;
574
+ // 1. EDITED: Explicitly look up the CORRECT default unit from the main dictionary.
575
+ // This is the information source we were previously ignoring.
576
+ const preferredUnit = (_DICTIONARIES$fld$var = _dictionaries.DICTIONARIES.fld[variable]) === null || _DICTIONARIES$fld$var === void 0 ? void 0 : _DICTIONARIES$fld$var.defaultUnit; // e.g., '°C' for '2t_2'
577
+
578
+ // 2. EDITED: Check if that preferred unit exists in the colormap definitions.
579
+ if (preferredUnit && defaultColormapData.units[preferredUnit]) {
580
+ const unitData = defaultColormapData.units[preferredUnit];
581
+ if (unitData && unitData.colormap) {
582
+ // 3. EDITED: If it exists, return ITS data. This is the correct path.
583
+ console.log(`[AguaceroCore] Using dictionary default unit '${preferredUnit}' for variable '${variable}'.`);
584
+ return {
585
+ colormap: unitData.colormap,
586
+ baseUnit: preferredUnit
587
+ };
588
+ }
589
+ }
590
+
591
+ // 4. EDITED: The old, buggy logic now serves as a FALLBACK ONLY.
592
+ // This will only run if the specified defaultUnit is missing from the colormap data.
593
+ console.warn(`[AguaceroCore] Default unit for '${variable}' not found in colormap. Using fallback.`);
569
594
  const availableUnits = Object.keys(defaultColormapData.units);
570
595
  if (availableUnits.length > 0) {
571
- const baseUnit = availableUnits[0];
572
- const unitData = defaultColormapData.units[baseUnit];
596
+ const fallbackUnit = availableUnits[0]; // e.g., '°F'
597
+ const unitData = defaultColormapData.units[fallbackUnit];
573
598
  if (unitData && unitData.colormap) {
574
599
  return {
575
600
  colormap: unitData.colormap,
576
- baseUnit: baseUnit
601
+ baseUnit: fallbackUnit
577
602
  };
578
603
  }
579
604
  }
580
605
  }
606
+ // --- END OF THE BUG FIX ---
607
+
581
608
  return {
582
609
  colormap: [],
583
610
  baseUnit: ''
@@ -218,7 +218,7 @@ const MODEL_CONFIGS = exports.MODEL_CONFIGS = {
218
218
  },
219
219
  'gfs': {
220
220
  max_zoom: 3,
221
- vars: ['refd_1000', 'vis_0', 'gust_runmax', "gh_tendency_500", 'bulk_shear_speedmb_500', 'bulk_shear_speedmb_700', 'bulk_shear_speedmb_850', 'bulk_shear_speedmb_925', '2t_2iso0', 'crain_total', 'crain_3', 'crain_6', 'crain_12', 'crain_24', 'crain_48', 'cicep_total', 'cicep_3', 'cicep_6', 'cicep_12', 'cicep_24', 'cicep_48', 'cfrzr_total', 'cfrzr_3', 'cfrzr_6', 'cfrzr_12', 'cfrzr_24', 'cfrzr_48', 'csnow_total', 'csnow_3', 'csnow_6', 'csnow_12', 'csnow_24', 'csnow_48', 'tp_0_total', 'tp_3', 'tp_6', 'tp_12', 'tp_24', 'tp_48', 'cin_0', 'wind_speed_700', 'wind_speed_200', 'divergence_200', 'd_925', 'tadv_300', 'w_850', 'rainRefl', 'icepRefl', 'snowRefl', 'frzrRefl', '2t_2', 'lftx_0', 'refc_0', 'fgen_850', 'hcc_0', 'r_700', 't_850', 't_850iso0', 'r_850', 'tcc_0', 'hlcy_3000', 'thickness', 'vo_850', 'wind_direction_2000', 'r_500', 'gh_500', 'wind_speed_500', '2d_2', 'cape_25500', 'mcc_0', 'w_500', 'pwat_0', 'divergence_850', 't_500', 'wind_speed_850', 'lcl', 'cape_0', 'tadv_850', 'tadv_700', 'theta2PVU', 'wind_speed_2000', 'lapse_rates_500700', 'vo_500', 'irsat', 't_700', 't_700iso0', 'cin_25500', 'ehi_3000', 'lcc_0', 'gh_850', 'wind_speed_925', 'gh_200', 'wind_speed_300', 'fgen_700', 'vo_700', 'd_850', 'thetaE', 'pres2PVU', 'd_700', 'crain', 'csnow', 'cicep', 'cfrzr', 'w_700', 'gust_0', 'ivt', 'atemp', 'cape_9000', 'r_925', 'mslma_0', 'w_925', 'cin_9000', 'mean700300mbRH', 'wind_speed_10', 't_925', 't_925iso0', 'gh_925', 'gh_700', 'gh_300', '2r_2'],
221
+ vars: ['2r_2', '2t_2', 'refd_1000', 'vis_0', 'gust_runmax', "gh_tendency_500", 'bulk_shear_speedmb_500', 'bulk_shear_speedmb_700', 'bulk_shear_speedmb_850', 'bulk_shear_speedmb_925', '2t_2iso0', 'crain_total', 'crain_3', 'crain_6', 'crain_12', 'crain_24', 'crain_48', 'cicep_total', 'cicep_3', 'cicep_6', 'cicep_12', 'cicep_24', 'cicep_48', 'cfrzr_total', 'cfrzr_3', 'cfrzr_6', 'cfrzr_12', 'cfrzr_24', 'cfrzr_48', 'csnow_total', 'csnow_3', 'csnow_6', 'csnow_12', 'csnow_24', 'csnow_48', 'tp_0_total', 'tp_3', 'tp_6', 'tp_12', 'tp_24', 'tp_48', 'cin_0', 'wind_speed_700', 'wind_speed_200', 'divergence_200', 'd_925', 'tadv_300', 'w_850', 'rainRefl', 'icepRefl', 'snowRefl', 'frzrRefl', 'lftx_0', 'refc_0', 'fgen_850', 'hcc_0', 'r_700', 't_850', 't_850iso0', 'r_850', 'tcc_0', 'hlcy_3000', 'thickness', 'vo_850', 'wind_direction_2000', 'r_500', 'gh_500', 'wind_speed_500', '2d_2', 'cape_25500', 'mcc_0', 'w_500', 'pwat_0', 'divergence_850', 't_500', 'wind_speed_850', 'lcl', 'cape_0', 'tadv_850', 'tadv_700', 'theta2PVU', 'wind_speed_2000', 'lapse_rates_500700', 'vo_500', 'irsat', 't_700', 't_700iso0', 'cin_25500', 'ehi_3000', 'lcc_0', 'gh_850', 'wind_speed_925', 'gh_200', 'wind_speed_300', 'fgen_700', 'vo_700', 'd_850', 'thetaE', 'pres2PVU', 'd_700', 'crain', 'csnow', 'cicep', 'cfrzr', 'w_700', 'gust_0', 'ivt', 'atemp', 'cape_9000', 'r_925', 'mslma_0', 'w_925', 'cin_9000', 'mean700300mbRH', 'wind_speed_10', 't_925', 't_925iso0', 'gh_925', 'gh_700', 'gh_300'],
222
222
  category: 'Global',
223
223
  name: 'GFS',
224
224
  bounds: [-180, -90, 180, 90],
package/dist/events.js CHANGED
@@ -17,8 +17,18 @@ class EventEmitter {
17
17
  if (!this.callbacks[event]) this.callbacks[event] = [];
18
18
  this.callbacks[event].push(cb);
19
19
  }
20
+
21
+ // --- THIS IS THE MISSING PIECE ---
22
+ // Add this method to allow listeners to be removed.
23
+ off(event, cb) {
24
+ const cbs = this.callbacks[event];
25
+ if (cbs) {
26
+ // Create a new array that excludes the callback we want to remove
27
+ this.callbacks[event] = cbs.filter(callback => callback !== cb);
28
+ }
29
+ }
20
30
  emit(event, data) {
21
- let cbs = this.callbacks[event];
31
+ const cbs = this.callbacks[event];
22
32
  if (cbs) {
23
33
  cbs.forEach(cb => cb(data));
24
34
  }
package/dist/index.js CHANGED
@@ -27,7 +27,20 @@ Object.defineProperty(exports, "THEME_CONFIGS", {
27
27
  return _mapStyles.THEME_CONFIGS;
28
28
  }
29
29
  });
30
+ Object.defineProperty(exports, "getUnitConversionFunction", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _unitConversions.getUnitConversionFunction;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "unitConversions", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _unitConversions.unitConversions;
40
+ }
41
+ });
30
42
  var _AguaceroCore = require("./AguaceroCore.js");
31
43
  var _events = require("./events.js");
32
44
  var _mapStyles = require("./map-styles.js");
33
- var _dictionaries = require("./dictionaries.js");
45
+ var _dictionaries = require("./dictionaries.js");
46
+ var _unitConversions = require("./unitConversions.js");
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@aguacerowx/javascript-sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "description": "Core SDK for fetching and processing Aguacero weather data.",
9
- "main": "dist/index.js",
9
+ "main": "src/index.js",
10
10
  "module": "src/index.js",
11
- "react-native": "dist/index.js",
11
+ "react-native": "src/index.js",
12
12
  "exports": {
13
13
  ".": {
14
14
  "import": "./src/index.js",
15
- "require": "./dist/index.js",
16
- "react-native": "./dist/index.js"
15
+ "require": "./src/index.js",
16
+ "react-native": "./src/index.js"
17
17
  }
18
18
  },
19
19
  "files": [
@@ -45,4 +45,4 @@
45
45
  "optional": true
46
46
  }
47
47
  }
48
- }
48
+ }
@@ -81,7 +81,7 @@ export class AguaceroCore extends EventEmitter {
81
81
  run: null,
82
82
  forecastHour: 0,
83
83
  visible: true,
84
- opacity: userLayerOptions.opacity ?? 0.85,
84
+ opacity: userLayerOptions.opacity ?? 1,
85
85
  units: options.initialUnit || 'imperial',
86
86
  shaderSmoothingEnabled: options.shaderSmoothingEnabled ?? true
87
87
  };
@@ -107,17 +107,21 @@ export class AguaceroCore extends EventEmitter {
107
107
  availableTimestamps = [...timestamps].reverse();
108
108
  }
109
109
 
110
- this.emit('state:change', {
110
+ const eventPayload = {
111
111
  ...this.state,
112
112
  availableModels: this.modelStatus ? Object.keys(this.modelStatus).sort() : [],
113
113
  availableRuns: this.modelStatus?.[this.state.model] || {},
114
114
  availableHours: this.state.isMRMS ? [] : (this.modelStatus?.[this.state.model]?.[this.state.date]?.[this.state.run] || []),
115
115
  availableVariables: this.getAvailableVariables(this.state.isMRMS ? 'mrms' : this.state.model),
116
+ // We need to confirm this line is working as expected.
117
+ availableMRMSVariables: this.getAvailableVariables('mrms'),
116
118
  availableTimestamps: availableTimestamps,
117
119
  isPlaying: this.isPlaying,
118
120
  colormap: displayColormap,
119
121
  colormapBaseUnit: toUnit,
120
- });
122
+ };
123
+
124
+ this.emit('state:change', eventPayload);
121
125
  }
122
126
 
123
127
  async initialize(options = {}) {
@@ -312,6 +316,10 @@ export class AguaceroCore extends EventEmitter {
312
316
  }
313
317
 
314
318
  async _loadGridData(state) {
319
+ if (this.isReactNative) {
320
+ console.warn(`[AguaceroCore] _loadGridData was called in React Native. This is a bypass. Data loading is handled natively.`);
321
+ return null;
322
+ }
315
323
  const { model, date, run, forecastHour, variable, isMRMS, mrmsTimestamp } = state;
316
324
  let effectiveSmoothing = 0;
317
325
  const customVariableSettings = this.customColormaps[variable];
@@ -334,30 +342,32 @@ export class AguaceroCore extends EventEmitter {
334
342
  return this.dataCache.get(dataUrlIdentifier);
335
343
  }
336
344
 
337
- // Create and store abort controller BEFORE creating the promise
345
+ // --- EDITED ---
346
+ // If we are in React Native, this function should NOT do any work.
347
+ // The native WeatherFrameProcessorModule is now responsible for all data loading.
348
+ // This function might still be called by a "cache miss" fallback, but it
349
+ // should not fetch data from JS anymore. We return null so the fallback knows
350
+ // that the native module is the only source of truth for new data.
351
+ if (this.isReactNative) {
352
+ console.warn(`_loadGridData was called in React Native for ${dataUrlIdentifier}. This should be handled by the native module. Returning null.`);
353
+ return null;
354
+ }
355
+
338
356
  const abortController = new AbortController();
339
357
  this.abortControllers.set(dataUrlIdentifier, abortController);
340
-
358
+
341
359
  const loadPromise = (async () => {
342
360
  if (!this.apiKey) {
343
361
  throw new Error('API key is not configured.');
344
362
  }
345
363
  try {
346
364
  const baseUrl = `${this.baseGridUrl}${resourcePath}`;
347
-
348
- // NEW: Construct the URL with the apiKey as a query parameter
349
365
  const urlWithApiKeyParam = `${baseUrl}?apiKey=${this.apiKey}`;
350
-
351
- // --- END OF MODIFICATION ---
352
-
353
-
354
- // The headers object remains the same, sending the key in the header as well
355
366
  const headers = { 'x-api-key': this.apiKey };
356
367
  if (this.bundleId) {
357
368
  headers['x-app-identifier'] = this.bundleId;
358
369
  }
359
370
 
360
- // Use the NEW url variable in the fetch call
361
371
  const response = await fetch(urlWithApiKeyParam, {
362
372
  headers: headers,
363
373
  signal: abortController.signal
@@ -369,20 +379,14 @@ export class AguaceroCore extends EventEmitter {
369
379
  const { data: b64Data, encoding } = await response.json();
370
380
  const compressedData = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
371
381
 
372
- let finalData;
373
-
374
- if (this.isReactNative) {
375
- const decompressedDeltas = decompress(compressedData);
376
- finalData = this._reconstructData(decompressedDeltas, encoding);
377
- } else {
378
- const requestId = this.workerRequestId++;
379
- const workerPromise = new Promise((resolve, reject) => {
380
- this.workerResolvers.set(requestId, { resolve, reject });
381
- });
382
- this.worker.postMessage({ requestId, compressedData, encoding }, [compressedData.buffer]);
383
- const result = await workerPromise;
384
- finalData = result.data;
385
- }
382
+ // This path is now ONLY for the web worker
383
+ const requestId = this.workerRequestId++;
384
+ const workerPromise = new Promise((resolve, reject) => {
385
+ this.workerResolvers.set(requestId, { resolve, reject });
386
+ });
387
+ this.worker.postMessage({ requestId, compressedData, encoding }, [compressedData.buffer]);
388
+ const result = await workerPromise;
389
+ const finalData = result.data;
386
390
 
387
391
  const transformedData = new Uint8Array(finalData.length);
388
392
  for (let i = 0; i < finalData.length; i++) {
@@ -390,7 +394,6 @@ export class AguaceroCore extends EventEmitter {
390
394
  transformedData[i] = signedValue + 128;
391
395
  }
392
396
 
393
- // Clean up abort controller on success
394
397
  this.abortControllers.delete(dataUrlIdentifier);
395
398
 
396
399
  return { data: transformedData, encoding };
@@ -570,13 +573,15 @@ export class AguaceroCore extends EventEmitter {
570
573
 
571
574
  _getTargetUnit(defaultUnit, system) {
572
575
  if (system === 'metric') {
573
- if (['°F', '°C'].includes(defaultUnit)) return 'celsius';
576
+ if (['°F', '°C', 'fahrenheit', 'celsius'].includes(defaultUnit)) return '°C';
574
577
  if (['kts', 'mph', 'm/s'].includes(defaultUnit)) return 'km/h';
575
578
  if (['in', 'mm', 'cm'].includes(defaultUnit)) return 'mm';
576
579
  }
577
- if (['°F', '°C'].includes(defaultUnit)) return 'fahrenheit';
578
- if (['kts', 'mph', 'm/s'].includes(defaultUnit)) return 'mph';
579
- if (['in', 'mm', 'cm'].includes(defaultUnit)) return 'in';
580
+ if (system === 'imperial') {
581
+ if (['°F', '°C', 'fahrenheit', 'celsius'].includes(defaultUnit)) return '°F';
582
+ if (['kts', 'mph', 'm/s'].includes(defaultUnit)) return 'mph';
583
+ if (['in', 'mm', 'cm'].includes(defaultUnit)) return 'in';
584
+ }
580
585
  return defaultUnit;
581
586
  }
582
587
 
@@ -278,7 +278,7 @@ export const MODEL_CONFIGS = {
278
278
  },
279
279
  'gfs': {
280
280
  max_zoom: 3,
281
- vars: ['refd_1000', 'vis_0', 'gust_runmax', "gh_tendency_500", 'bulk_shear_speedmb_500', 'bulk_shear_speedmb_700', 'bulk_shear_speedmb_850', 'bulk_shear_speedmb_925','2t_2iso0', 'crain_total', 'crain_3', 'crain_6', 'crain_12', 'crain_24', 'crain_48', 'cicep_total', 'cicep_3', 'cicep_6', 'cicep_12', 'cicep_24', 'cicep_48', 'cfrzr_total', 'cfrzr_3', 'cfrzr_6', 'cfrzr_12', 'cfrzr_24', 'cfrzr_48', 'csnow_total', 'csnow_3', 'csnow_6', 'csnow_12', 'csnow_24', 'csnow_48', 'tp_0_total', 'tp_3', 'tp_6', 'tp_12', 'tp_24', 'tp_48', 'cin_0', 'wind_speed_700', 'wind_speed_200', 'divergence_200', 'd_925', 'tadv_300', 'w_850', 'rainRefl', 'icepRefl', 'snowRefl', 'frzrRefl', '2t_2', 'lftx_0', 'refc_0', 'fgen_850', 'hcc_0', 'r_700', 't_850', 't_850iso0', 'r_850', 'tcc_0', 'hlcy_3000', 'thickness', 'vo_850', 'wind_direction_2000', 'r_500', 'gh_500', 'wind_speed_500', '2d_2', 'cape_25500', 'mcc_0', 'w_500', 'pwat_0', 'divergence_850', 't_500', 'wind_speed_850', 'lcl', 'cape_0', 'tadv_850', 'tadv_700', 'theta2PVU', 'wind_speed_2000', 'lapse_rates_500700', 'vo_500', 'irsat', 't_700', 't_700iso0', 'cin_25500', 'ehi_3000', 'lcc_0', 'gh_850', 'wind_speed_925', 'gh_200', 'wind_speed_300', 'fgen_700', 'vo_700', 'd_850', 'thetaE', 'pres2PVU', 'd_700', 'crain', 'csnow', 'cicep', 'cfrzr', 'w_700', 'gust_0', 'ivt', 'atemp', 'cape_9000', 'r_925', 'mslma_0', 'w_925', 'cin_9000', 'mean700300mbRH', 'wind_speed_10', 't_925', 't_925iso0', 'gh_925', 'gh_700', 'gh_300', '2r_2'],
281
+ vars: ['2r_2', '2t_2', 'refd_1000', 'vis_0', 'gust_runmax', "gh_tendency_500", 'bulk_shear_speedmb_500', 'bulk_shear_speedmb_700', 'bulk_shear_speedmb_850', 'bulk_shear_speedmb_925','2t_2iso0', 'crain_total', 'crain_3', 'crain_6', 'crain_12', 'crain_24', 'crain_48', 'cicep_total', 'cicep_3', 'cicep_6', 'cicep_12', 'cicep_24', 'cicep_48', 'cfrzr_total', 'cfrzr_3', 'cfrzr_6', 'cfrzr_12', 'cfrzr_24', 'cfrzr_48', 'csnow_total', 'csnow_3', 'csnow_6', 'csnow_12', 'csnow_24', 'csnow_48', 'tp_0_total', 'tp_3', 'tp_6', 'tp_12', 'tp_24', 'tp_48', 'cin_0', 'wind_speed_700', 'wind_speed_200', 'divergence_200', 'd_925', 'tadv_300', 'w_850', 'rainRefl', 'icepRefl', 'snowRefl', 'frzrRefl', 'lftx_0', 'refc_0', 'fgen_850', 'hcc_0', 'r_700', 't_850', 't_850iso0', 'r_850', 'tcc_0', 'hlcy_3000', 'thickness', 'vo_850', 'wind_direction_2000', 'r_500', 'gh_500', 'wind_speed_500', '2d_2', 'cape_25500', 'mcc_0', 'w_500', 'pwat_0', 'divergence_850', 't_500', 'wind_speed_850', 'lcl', 'cape_0', 'tadv_850', 'tadv_700', 'theta2PVU', 'wind_speed_2000', 'lapse_rates_500700', 'vo_500', 'irsat', 't_700', 't_700iso0', 'cin_25500', 'ehi_3000', 'lcc_0', 'gh_850', 'wind_speed_925', 'gh_200', 'wind_speed_300', 'fgen_700', 'vo_700', 'd_850', 'thetaE', 'pres2PVU', 'd_700', 'crain', 'csnow', 'cicep', 'cfrzr', 'w_700', 'gust_0', 'ivt', 'atemp', 'cape_9000', 'r_925', 'mslma_0', 'w_925', 'cin_9000', 'mean700300mbRH', 'wind_speed_10', 't_925', 't_925iso0', 'gh_925', 'gh_700', 'gh_300'],
282
282
  category: 'Global',
283
283
  name: 'GFS',
284
284
  bounds: [-180, -90, 180, 90],
package/src/events.js CHANGED
@@ -7,12 +7,24 @@ export class EventEmitter {
7
7
  constructor() {
8
8
  this.callbacks = {};
9
9
  }
10
+
10
11
  on(event, cb) {
11
12
  if (!this.callbacks[event]) this.callbacks[event] = [];
12
13
  this.callbacks[event].push(cb);
13
14
  }
15
+
16
+ // --- THIS IS THE MISSING PIECE ---
17
+ // Add this method to allow listeners to be removed.
18
+ off(event, cb) {
19
+ const cbs = this.callbacks[event];
20
+ if (cbs) {
21
+ // Create a new array that excludes the callback we want to remove
22
+ this.callbacks[event] = cbs.filter(callback => callback !== cb);
23
+ }
24
+ }
25
+
14
26
  emit(event, data) {
15
- let cbs = this.callbacks[event];
27
+ const cbs = this.callbacks[event];
16
28
  if (cbs) {
17
29
  cbs.forEach(cb => cb(data));
18
30
  }
package/src/index.js CHANGED
@@ -5,11 +5,13 @@ import { AguaceroCore } from './AguaceroCore.js';
5
5
  import { EventEmitter } from './events.js';
6
6
  import { THEME_CONFIGS } from './map-styles.js';
7
7
  import { DICTIONARIES } from './dictionaries.js';
8
+ import { getUnitConversionFunction } from './unitConversions.js';
8
9
 
9
10
  // Now, export them all so other packages can import them.
10
11
  export {
11
12
  AguaceroCore,
12
13
  EventEmitter,
13
14
  THEME_CONFIGS,
14
- DICTIONARIES
15
+ DICTIONARIES,
16
+ getUnitConversionFunction
15
17
  };