@aguacerowx/mapsgl 0.0.32 → 0.0.42

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.
Files changed (35) hide show
  1. package/index.js +34 -2
  2. package/package.json +14 -3
  3. package/src/GridRenderLayer.js +105 -86
  4. package/src/MapManager.js +47 -15
  5. package/src/NexradSitesOverlay.js +148 -0
  6. package/src/NexradWeatherController.js +491 -0
  7. package/src/NwsWatchesWarningsOverlay.js +768 -0
  8. package/src/SatelliteShaderManager.js +999 -0
  9. package/src/WeatherLayerManager.js +866 -139
  10. package/src/WorkerPool.js +340 -0
  11. package/src/nexrad/MapboxRadarLayer.bundled.js +810 -0
  12. package/src/nexrad/MapboxRadarLayer.ts +784 -0
  13. package/src/nexrad/PreprocessedSweepParser.ts +226 -0
  14. package/src/nexrad/buildRadarRayGeometry.ts +97 -0
  15. package/src/nexrad/level3StormRelative.ts +116 -0
  16. package/src/nexrad/loadNexradSites.ts +41 -0
  17. package/src/nexrad/nexradArchiveCache.ts +64 -0
  18. package/src/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js +91 -0
  19. package/src/nexrad/nexradCrossSectionSampleAtLatLon.ts +121 -0
  20. package/src/nexrad/nexradLevel3Products.ts +549 -0
  21. package/src/nexrad/nexradMapboxFrameOpts.js +106 -0
  22. package/src/nexrad/radarArchiveCore.bundled.js +4206 -0
  23. package/src/nexrad/radarArchiveCore.bundled.js.map +7 -0
  24. package/src/nexrad/radarArchiveCore.ts +1737 -0
  25. package/src/nexrad/radarDecode.worker.bundled.js +809 -0
  26. package/src/nexrad/radarDecode.worker.ts +227 -0
  27. package/src/nexrad/radarFrameGpuMatch.bundled.js +79 -0
  28. package/src/nexrad/radarFrameGpuMatch.ts +111 -0
  29. package/src/nwsAlertsSupport.js +860 -0
  30. package/src/nwsEventColorsDefaults.js +133 -0
  31. package/src/nwsSdkConstants.js +360 -0
  32. package/src/nwsWarningCustomizationKey.gen.js +496 -0
  33. package/src/satelliteDefaultColormaps.js +37 -0
  34. package/src/satelliteKtxWorker.js +225 -0
  35. package/src/satelliteShader.js +17 -0
package/index.js CHANGED
@@ -3,10 +3,42 @@
3
3
  // Import the specific classes you want to make public from this package's internal files.
4
4
  import { WeatherLayerManager } from './src/WeatherLayerManager.js';
5
5
  import { MapManager } from './src/MapManager.js';
6
+ import { NwsWatchesWarningsOverlay, NWS_DEFAULT_LINE_BEFORE_LAYER_ID } from './src/NwsWatchesWarningsOverlay.js';
7
+ import {
8
+ collectNwsExpressionEventKeys,
9
+ NWS_ALERT_EVENT_NAMES,
10
+ NWS_EVENT_COLORS,
11
+ NWS_SDK_EVENT_COLOR,
12
+ NWS_SDK_EVENT_NAMES,
13
+ } from './src/nwsSdkConstants.js';
14
+ import {
15
+ filterNwsAlertsByIncludedList,
16
+ filterNwsAlertsByScope,
17
+ filterToConvectiveSdkEvents,
18
+ isNwsEventIncludedInAllowlist,
19
+ SDK_ALLOWED_EVENT_SET,
20
+ } from './src/nwsAlertsSupport.js';
6
21
 
7
22
  // Now, export them both so your test app can import them.
8
23
  // This is the "public face" of your @aguacerowx/mapsgl package.
9
24
  export {
10
25
  WeatherLayerManager,
11
- MapManager
12
- };
26
+ MapManager,
27
+ NwsWatchesWarningsOverlay,
28
+ NWS_DEFAULT_LINE_BEFORE_LAYER_ID,
29
+ collectNwsExpressionEventKeys,
30
+ NWS_ALERT_EVENT_NAMES,
31
+ NWS_EVENT_COLORS,
32
+ NWS_SDK_EVENT_COLOR,
33
+ NWS_SDK_EVENT_NAMES,
34
+ filterNwsAlertsByIncludedList,
35
+ filterNwsAlertsByScope,
36
+ filterToConvectiveSdkEvents,
37
+ isNwsEventIncludedInAllowlist,
38
+ SDK_ALLOWED_EVENT_SET,
39
+ };
40
+
41
+ export {
42
+ DEFAULT_SATELLITE_LONGWAVE_IR_COLORMAP,
43
+ DEFAULT_SATELLITE_WATER_VAPOR_COLORMAP,
44
+ } from './src/satelliteDefaultColormaps.js';
package/package.json CHANGED
@@ -1,19 +1,30 @@
1
1
  {
2
2
  "name": "@aguacerowx/mapsgl",
3
- "version": "0.0.32",
3
+ "version": "0.0.42",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "main": "index.js",
9
9
  "type": "module",
10
+ "scripts": {
11
+ "prepublishOnly": "npm run bundle-nexrad",
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",
13
+ "gen:nws-key": "esbuild ../../../aguacero-frontend/src/components/WarningsMenu/nwsWarningCustomizationKey.ts --bundle --format=esm --platform=neutral --outfile=src/nwsWarningCustomizationKey.gen.js"
14
+ },
10
15
  "files": [
11
16
  "index.js",
12
17
  "src"
13
18
  ],
19
+ "devDependencies": {
20
+ "esbuild": "^0.21.5"
21
+ },
14
22
  "dependencies": {
15
- "@aguacerowx/javascript-sdk": "^0.0.18",
23
+ "@aguacerowx/javascript-sdk": "^0.0.20",
24
+ "buffer": "^6.0.3",
25
+ "fzstd": "^0.1.1",
16
26
  "mapbox-gl": "^3.4.0",
17
- "proj4": "^2.11.0"
27
+ "proj4": "^2.11.0",
28
+ "seek-bzip": "^2.0.0"
18
29
  }
19
30
  }
@@ -2,6 +2,11 @@ import proj4 from 'proj4';
2
2
 
3
3
  const MERCATOR_SAFE_LIMIT = 89;
4
4
 
5
+ /** Mapbox GL 3+ uses a WebGL2 context; LUMINANCE is not a valid sized internal format in WebGL2. */
6
+ function isWebGL2Context(gl) {
7
+ return typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext;
8
+ }
9
+
5
10
  function hrdpsObliqueTransform(rotated_lon, rotated_lat) {
6
11
  const o_lat_p = 53.91148;
7
12
  const o_lon_p = 245.305142;
@@ -27,7 +32,18 @@ function hrdpsObliqueTransform(rotated_lon, rotated_lat) {
27
32
  }
28
33
 
29
34
  export class GridRenderLayer {
30
- constructor(id) {
35
+ /**
36
+ * @param {string | { layerId?: string, id?: string }} idOrOpts - Mapbox custom layer id, or `{ layerId }` / `{ id }` (RN / prop passthrough parity).
37
+ */
38
+ constructor(idOrOpts) {
39
+ let id;
40
+ if (typeof idOrOpts === 'string') {
41
+ id = idOrOpts;
42
+ } else if (idOrOpts != null && typeof idOrOpts === 'object') {
43
+ id = idOrOpts.layerId ?? idOrOpts.id;
44
+ } else {
45
+ id = idOrOpts;
46
+ }
31
47
  this.id = id;
32
48
  this.type = 'custom';
33
49
  this.renderingMode = '2d';
@@ -110,16 +126,16 @@ export class GridRenderLayer {
110
126
  try {
111
127
  return this.map.transform.MercatorCoordinate.fromLngLat({ lon, lat });
112
128
  } catch (e) {
113
- console.warn('[GridLayer] Failed to use map.transform.MercatorCoordinate:', e);
129
+ // fall through to manual projection
114
130
  }
115
131
  }
116
-
132
+
117
133
  // Fallback to imported mapboxgl MercatorCoordinate
118
134
  if (mapboxgl && mapboxgl.MercatorCoordinate) {
119
135
  try {
120
136
  return mapboxgl.MercatorCoordinate.fromLngLat([lon, lat]);
121
137
  } catch (e) {
122
- console.warn('[GridLayer] Failed to use mapboxgl.MercatorCoordinate:', e);
138
+ // fall through to manual projection
123
139
  }
124
140
  }
125
141
 
@@ -404,12 +420,13 @@ export class GridRenderLayer {
404
420
  raw_value = dequantize_val(total_value / total_weight);
405
421
  }
406
422
  }
407
-
408
- if (raw_value < u_data_range.x) {
409
- discard;
410
- }
411
423
 
412
424
  float converted_value = convert_units(raw_value);
425
+
426
+ // Compare against u_data_range in the same unit space as the colormap (after unit conversion).
427
+ if (converted_value < u_data_range.x) {
428
+ discard;
429
+ }
413
430
 
414
431
  float colormap_coord = clamp((converted_value - u_data_range.x) / (u_data_range.y - u_data_range.x), 0.0, 1.0);
415
432
 
@@ -457,6 +474,39 @@ export class GridRenderLayer {
457
474
  }
458
475
  }
459
476
 
477
+ /**
478
+ * Upload quantized grid bytes (Uint8, centered at 128) to the active 2D texture.
479
+ * WebGL2: R8 + RED + UNSIGNED_BYTE (LUMINANCE is invalid). WebGL1: LUMINANCE.
480
+ */
481
+ _uploadQuantizedGridTexture(tex, nx, ny, dataArray) {
482
+ const gl = this.gl;
483
+ gl.bindTexture(gl.TEXTURE_2D, tex);
484
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
485
+ if (isWebGL2Context(gl)) {
486
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, nx, ny, 0, gl.RED, gl.UNSIGNED_BYTE, dataArray);
487
+ } else {
488
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, nx, ny, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataArray);
489
+ }
490
+ }
491
+
492
+ /**
493
+ * After updateDataTexture, register the same GPU texture under timeKey so the slider can
494
+ * switch back without re-fetching (avoids duplicating VRAM).
495
+ */
496
+ registerCurrentDataTextureAsPreloaded(timeKey) {
497
+ if (!this.gl || !this.dataTexture || this.encoding == null) {
498
+ return;
499
+ }
500
+ if (!this.preloadedTextures) this.preloadedTextures = new Map();
501
+ if (this.preloadedTextures.has(timeKey)) return;
502
+ this.preloadedTextures.set(timeKey, {
503
+ tex: this.dataTexture,
504
+ encoding: this.encoding,
505
+ nx: this.textureWidth,
506
+ ny: this.textureHeight
507
+ });
508
+ }
509
+
460
510
  // Call this during preload to store a pre-uploaded GPU texture
461
511
  storePreloadedTexture(timeKey, data, encoding, nx, ny) {
462
512
  if (!this.gl) return;
@@ -464,13 +514,12 @@ export class GridRenderLayer {
464
514
 
465
515
  const tex = gl.createTexture();
466
516
  gl.bindTexture(gl.TEXTURE_2D, tex);
467
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
468
517
 
469
518
  let dataArray = data instanceof Uint8Array ? data
470
519
  : data.buffer instanceof ArrayBuffer ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
471
520
  : new Uint8Array(data);
472
521
 
473
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, nx, ny, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataArray);
522
+ this._uploadQuantizedGridTexture(tex, nx, ny, dataArray);
474
523
 
475
524
  const filter = this.noSmoothing ? gl.NEAREST : gl.LINEAR;
476
525
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
@@ -480,9 +529,11 @@ export class GridRenderLayer {
480
529
  gl.bindTexture(gl.TEXTURE_2D, null);
481
530
 
482
531
  if (!this.preloadedTextures) this.preloadedTextures = new Map();
483
- // Free old texture for this key if it exists
484
532
  if (this.preloadedTextures.has(timeKey)) {
485
- gl.deleteTexture(this.preloadedTextures.get(timeKey).tex);
533
+ const prev = this.preloadedTextures.get(timeKey);
534
+ if (prev.tex && prev.tex !== this.dataTexture) {
535
+ gl.deleteTexture(prev.tex);
536
+ }
486
537
  }
487
538
  this.preloadedTextures.set(timeKey, { tex, encoding, nx, ny });
488
539
  }
@@ -499,15 +550,6 @@ export class GridRenderLayer {
499
550
  return true;
500
551
  }
501
552
 
502
- // Call in onRemove() to clean up
503
- clearPreloadedTextures() {
504
- if (!this.gl || !this.preloadedTextures) return;
505
- for (const { tex } of this.preloadedTextures.values()) {
506
- this.gl.deleteTexture(tex);
507
- }
508
- this.preloadedTextures.clear();
509
- }
510
-
511
553
  updateGeometry(corners, gridDef) {
512
554
 
513
555
  // Cache the geometry
@@ -523,8 +565,7 @@ export class GridRenderLayer {
523
565
  corners.lon_bl, corners.lat_bl, corners.lon_br, corners.lat_br];
524
566
 
525
567
  if (coordValues.some(coord => !isFinite(coord))) {
526
- console.error('[GridLayer] Invalid corner coordinates:', corners);
527
- return;
568
+ return;
528
569
  }
529
570
 
530
571
  const gridType = gridDef.type;
@@ -669,17 +710,13 @@ export class GridRenderLayer {
669
710
  adjustedCorners.lon_br, adjustedCorners.lat_br];
670
711
 
671
712
  if (adjustedCoordValues.some(coord => !isFinite(coord))) {
672
- console.error('[GridLayer] Invalid adjusted corner coordinates:', adjustedCorners);
673
- return;
713
+ return;
674
714
  }
675
715
 
676
716
  // Additional validation: check if latitudes are in valid range
677
717
  const latValues = [adjustedCorners.lat_tl, adjustedCorners.lat_tr, adjustedCorners.lat_bl, adjustedCorners.lat_br];
678
718
  if (latValues.some(lat => lat < -90 || lat > 90)) {
679
- console.error('[GridLayer] Invalid latitude values in corners:', adjustedCorners);
680
- console.error('[GridLayer] Original corners were:', corners);
681
- console.error('[GridLayer] Grid definition:', gridDef);
682
- return;
719
+ return;
683
720
  }
684
721
 
685
722
  // Calculate longitude span to handle wrapping
@@ -745,13 +782,7 @@ export class GridRenderLayer {
745
782
 
746
783
  // Validate coordinates
747
784
  if (!isFinite(lon) || !isFinite(lat)) {
748
- console.error(`[GridLayer] Invalid interpolated coordinates at ${row},${col}: lon=${lon}, lat=${lat}`);
749
- console.error(`[GridLayer] Interpolation inputs:`, {
750
- t_x, t_y,
751
- corners: adjustedCorners,
752
- gridType: isGFSType ? 'GFS' : (isECMWFType ? 'ECMWF' : (isIconModel ? 'ICON' : 'OTHER'))
753
- });
754
- continue;
785
+ continue;
755
786
  }
756
787
 
757
788
  // Normalize longitude to [-180, 180] range
@@ -762,8 +793,7 @@ export class GridRenderLayer {
762
793
  const mercator = this.getMercatorCoordinate(normalizedLon, lat);
763
794
 
764
795
  if (!isFinite(mercator.x) || !isFinite(mercator.y)) {
765
- console.error(`[GridLayer] Invalid Mercator coordinates: x=${mercator.x}, y=${mercator.y}`);
766
- continue;
796
+ continue;
767
797
  }
768
798
 
769
799
  vertices.push(mercator.x, mercator.y, tex_u, tex_v);
@@ -834,8 +864,7 @@ export class GridRenderLayer {
834
864
  const [lon, lat] = hrdpsObliqueTransform(rot_lon, rot_lat);
835
865
 
836
866
  if (!isFinite(lon) || !isFinite(lat)) {
837
- console.warn(`[GridLayer] Invalid HRDPS transformed coordinates at ${row},${col}: lon=${lon}, lat=${lat}`);
838
- continue;
867
+ continue;
839
868
  }
840
869
 
841
870
  const clampedLat = Math.max(-MERCATOR_SAFE_LIMIT, Math.min(MERCATOR_SAFE_LIMIT, lat));
@@ -843,8 +872,7 @@ export class GridRenderLayer {
843
872
  const mercator = this.getMercatorCoordinate(lon, clampedLat);
844
873
 
845
874
  if (!isFinite(mercator.x) || !isFinite(mercator.y)) {
846
- console.warn(`[GridLayer] Invalid HRDPS Mercator coordinates: x=${mercator.x}, y=${mercator.y}`);
847
- continue;
875
+ continue;
848
876
  }
849
877
 
850
878
  // Texture coordinates - corrected for HRDPS orientation
@@ -854,8 +882,7 @@ export class GridRenderLayer {
854
882
  vertices.push(mercator.x, mercator.y, tex_u, tex_v);
855
883
 
856
884
  } catch (error) {
857
- console.warn(`[GridLayer] HRDPS manual transformation error at ${row},${col}:`, error);
858
- continue;
885
+ continue;
859
886
  }
860
887
  }
861
888
  }
@@ -941,8 +968,7 @@ export class GridRenderLayer {
941
968
  }
942
969
 
943
970
  if (lon_raw > 0) {
944
- console.warn(`[GridLayer] Rejecting suspicious positive longitude: ${lon} at [${row},${col}]`);
945
- vertexGrid[row][col] = null;
971
+ vertexGrid[row][col] = null;
946
972
  continue;
947
973
  }
948
974
 
@@ -987,8 +1013,7 @@ export class GridRenderLayer {
987
1013
  }
988
1014
 
989
1015
  if (vertices.length === 0) {
990
- console.error(`[GridLayer] No valid vertices generated for polar stereographic model`);
991
- return;
1016
+ return;
992
1017
  }
993
1018
 
994
1019
  // Second pass: generate indices (this logic remains the same)
@@ -1080,8 +1105,7 @@ export class GridRenderLayer {
1080
1105
  const [lon, lat] = proj4(projectionString, wgs84, [proj_x, proj_y]);
1081
1106
 
1082
1107
  if (lon > 0) {
1083
- console.warn(`[GridLayer] Rejecting suspicious positive longitude: ${lon} at [${row},${col}]`);
1084
- vertexGrid[row][col] = null;
1108
+ vertexGrid[row][col] = null;
1085
1109
  continue;
1086
1110
  }
1087
1111
 
@@ -1114,16 +1138,14 @@ export class GridRenderLayer {
1114
1138
  validVertexCount++;
1115
1139
 
1116
1140
  } catch (error) {
1117
- console.warn(`[GridLayer] Projection error at [${row},${col}] with coords [${proj_x}, ${proj_y}]:`, error);
1118
- vertexGrid[row][col] = null;
1141
+ vertexGrid[row][col] = null;
1119
1142
  continue;
1120
1143
  }
1121
1144
  }
1122
1145
  }
1123
1146
 
1124
1147
  if (vertices.length === 0) {
1125
- console.error(`[GridLayer] No valid vertices generated for ${this.modelName}`);
1126
- return;
1148
+ return;
1127
1149
  }
1128
1150
 
1129
1151
  // Second pass: generate indices only for quads where all 4 vertices are valid
@@ -1153,8 +1175,7 @@ export class GridRenderLayer {
1153
1175
 
1154
1176
  // Check for potential dateline issues and log them
1155
1177
  if (validMaxLon - validMinLon > 180) {
1156
- console.warn(`[GridLayer] ${this.modelName} longitude span is ${(validMaxLon - validMinLon).toFixed(2)}° - this might indicate projection issues`);
1157
- }
1178
+ }
1158
1179
 
1159
1180
  const vertexData = new Float32Array(vertices);
1160
1181
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
@@ -1172,14 +1193,12 @@ export class GridRenderLayer {
1172
1193
  this.vertexCount = vertices.length / 4;
1173
1194
 
1174
1195
  } else {
1175
- console.error(`[GridLayer] Unsupported grid type: ${gridType}`);
1176
- return;
1196
+ return;
1177
1197
  }
1178
1198
  }
1179
1199
 
1180
1200
  updateDataTexture(data, encoding, nx, ny) {
1181
1201
  if (!this.gl || !data) {
1182
- console.warn('❌ updateDataTexture early exit - missing gl or data');
1183
1202
  return;
1184
1203
  }
1185
1204
 
@@ -1201,12 +1220,6 @@ export class GridRenderLayer {
1201
1220
  const expectedSize = nx * ny;
1202
1221
 
1203
1222
  if (data.length === 0 || data.length < expectedSize) {
1204
- console.warn('⚠️ Data array is empty or too small - NO TEXTURE WILL BE CREATED', {
1205
- dataLength: data.length,
1206
- expected: expectedSize,
1207
- nx: nx,
1208
- ny: ny
1209
- });
1210
1223
  return;
1211
1224
  }
1212
1225
 
@@ -1224,15 +1237,7 @@ export class GridRenderLayer {
1224
1237
  this.dataTexture = gl.createTexture();
1225
1238
  gl.bindTexture(gl.TEXTURE_2D, this.dataTexture);
1226
1239
 
1227
- try {
1228
- // FIX: Set pixel store alignment to 1 to handle widths not divisible by 4
1229
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1230
-
1231
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, nx, ny, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataArray);
1232
- } catch (error) {
1233
- console.error('❌ texImage2D failed:', error);
1234
- throw error;
1235
- }
1240
+ this._uploadQuantizedGridTexture(this.dataTexture, nx, ny, dataArray);
1236
1241
 
1237
1242
  // Set texture parameters
1238
1243
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
@@ -1302,11 +1307,15 @@ export class GridRenderLayer {
1302
1307
  }
1303
1308
 
1304
1309
  render(gl, matrix) {
1305
- if (!this.dataTexture) {
1306
- console.log('⏭️ Skipping render - no texture available');
1310
+ if (!this.dataTexture || !this.encoding) {
1311
+ return;
1312
+ }
1313
+ if (!this.colormapTexture) {
1314
+ return;
1315
+ }
1316
+ if (!this.program || !this.vertexBuffer || !this.indexBuffer) {
1307
1317
  return;
1308
1318
  }
1309
- if (!this.program || !this.encoding || !this.vertexBuffer || !this.indexBuffer) return;
1310
1319
  gl.useProgram(this.program);
1311
1320
 
1312
1321
  if (this.u_is_ptype) {
@@ -1340,30 +1349,40 @@ export class GridRenderLayer {
1340
1349
 
1341
1350
  onRemove() {
1342
1351
  if (!this.gl) return;
1352
+ const gl = this.gl;
1343
1353
 
1344
1354
  if (this.program) {
1345
- this.gl.deleteProgram(this.program);
1355
+ gl.deleteProgram(this.program);
1346
1356
  this.program = null;
1347
1357
  }
1348
1358
  if (this.vertexBuffer) {
1349
- this.gl.deleteBuffer(this.vertexBuffer);
1359
+ gl.deleteBuffer(this.vertexBuffer);
1350
1360
  this.vertexBuffer = null;
1351
1361
  }
1352
1362
  if (this.indexBuffer) {
1353
- this.gl.deleteBuffer(this.indexBuffer);
1363
+ gl.deleteBuffer(this.indexBuffer);
1354
1364
  this.indexBuffer = null;
1355
1365
  }
1356
- if (this.dataTexture) {
1357
- this.gl.deleteTexture(this.dataTexture);
1358
- this.dataTexture = null;
1366
+
1367
+ const gridTextures = new Set();
1368
+ if (this.dataTexture) gridTextures.add(this.dataTexture);
1369
+ if (this.preloadedTextures) {
1370
+ for (const { tex } of this.preloadedTextures.values()) {
1371
+ if (tex) gridTextures.add(tex);
1372
+ }
1373
+ this.preloadedTextures.clear();
1359
1374
  }
1375
+ for (const tex of gridTextures) {
1376
+ gl.deleteTexture(tex);
1377
+ }
1378
+ this.dataTexture = null;
1379
+
1360
1380
  if (this.colormapTexture) {
1361
- this.gl.deleteTexture(this.colormapTexture);
1381
+ gl.deleteTexture(this.colormapTexture);
1362
1382
  this.colormapTexture = null;
1363
1383
  }
1364
-
1384
+
1365
1385
  this.map = null;
1366
1386
  this.gl = null;
1367
- this.clearPreloadedTextures();
1368
1387
  }
1369
1388
  }
package/src/MapManager.js CHANGED
@@ -41,13 +41,28 @@ function isObject(item) {
41
41
  const BASE_STYLE_URL = 'mapbox://styles/aguacerowx/cmfvox8mq004u01qm5nlg7qkt';
42
42
 
43
43
  export class MapManager extends EventEmitter {
44
+ /**
45
+ * @param {string} containerId - DOM element id for the Mapbox map container.
46
+ * @param {object} options
47
+ * @param {string} [options.accessToken] - Mapbox access token (required unless `mapboxToken` is set).
48
+ * @param {string} [options.mapboxToken] - Alias for `accessToken` (e.g. React Native parity).
49
+ * @param {object} [options.mapOptions] - Passed through to `mapboxgl.Map` (after defaults); `style` here is used unless a valid custom style + anchor pair is set.
50
+ * @param {object} [options.customStyles] - Per-theme style overrides merged into {@link THEME_CONFIGS}.
51
+ * @param {'light'|'dark'} [options.defaultTheme]
52
+ * @param {'light'|'dark'} [options.theme] - Alias for `defaultTheme`.
53
+ * @param {string} [options.styleUrl] - Custom Mapbox style URL (alias: `styleURL`).
54
+ * @param {string} [options.styleURL] - Same as `styleUrl`.
55
+ * @param {string} [options.weatherBeforeLayerId] - Style layer id to insert Aguacero layers below (alias: `belowID`).
56
+ * @param {string} [options.belowID] - Same as `weatherBeforeLayerId`. With `styleUrl`/`styleURL`, required for correct stacking; default Aguacero style uses `AML_-_terrain` in {@link WeatherLayerManager} when omitted.
57
+ */
44
58
  constructor(containerId, options = {}) {
45
59
  super();
46
- if (!containerId || !options.accessToken) {
60
+ const accessToken = options.accessToken || options.mapboxToken;
61
+ if (!containerId || !accessToken) {
47
62
  throw new Error('A container ID and a Mapbox access token are required.');
48
63
  }
49
64
 
50
- mapboxgl.accessToken = options.accessToken;
65
+ mapboxgl.accessToken = accessToken;
51
66
 
52
67
  // --- THE FIX IS HERE ---
53
68
 
@@ -57,15 +72,12 @@ export class MapManager extends EventEmitter {
57
72
 
58
73
  // 2. If developer provides custom styles, merge them into the defaults
59
74
  if (options.customStyles) {
60
- console.log('[MapManager] Custom styles provided. Merging...');
61
75
  if (options.customStyles.light) {
62
76
  lightTheme = deepMerge(lightTheme, options.customStyles.light);
63
77
  }
64
78
  if (options.customStyles.dark) {
65
79
  darkTheme = deepMerge(darkTheme, options.customStyles.dark);
66
80
  }
67
- // Log to confirm the merge was successful
68
- console.log('[MapManager] Final merged dark theme:', darkTheme);
69
81
  }
70
82
 
71
83
  // 3. Store the final, potentially merged, themes
@@ -76,22 +88,47 @@ export class MapManager extends EventEmitter {
76
88
 
77
89
  // --- END OF FIX ---
78
90
 
79
- const defaultThemeName = options.defaultTheme || 'light';
91
+ const defaultThemeName = options.theme || options.defaultTheme || 'light';
80
92
  this.currentCustomizations = this.themes[defaultThemeName];
81
93
  this.currentThemeName = defaultThemeName;
82
94
 
83
95
  this.weatherLayerManagers = new Map();
84
-
96
+
97
+ const mapOptions = options.mapOptions || {};
98
+ let initialStyle = mapOptions.style !== undefined ? mapOptions.style : BASE_STYLE_URL;
99
+ /** @type {string | null} */
100
+ let weatherBeforeLayerId = null;
101
+
102
+ const customStyleUrl = options.styleURL || options.styleUrl;
103
+ const customBeforeId = options.belowID || options.weatherBeforeLayerId;
104
+ if (customStyleUrl) {
105
+ if (customBeforeId && String(customBeforeId).trim()) {
106
+ initialStyle = customStyleUrl;
107
+ weatherBeforeLayerId = String(customBeforeId).trim();
108
+ } else {
109
+ console.warn(
110
+ '[MapManager] A custom style (`styleUrl` / `styleURL`) was provided without a non-empty `weatherBeforeLayerId` / `belowID`. ' +
111
+ 'Weather layers need a style layer id to insert below; keeping the default Aguacero style.'
112
+ );
113
+ if (mapOptions.style === undefined) {
114
+ initialStyle = BASE_STYLE_URL;
115
+ }
116
+ }
117
+ }
118
+
119
+ this.weatherBeforeLayerId = weatherBeforeLayerId;
120
+
85
121
  this.map = new mapboxgl.Map({
86
122
  container: containerId,
87
- style: BASE_STYLE_URL,
88
123
  center: [-98, 39],
89
124
  zoom: 3.5,
90
- ...options.mapOptions
125
+ ...mapOptions,
126
+ style: initialStyle,
91
127
  });
128
+
129
+ this.map.__aguaceroMapsgl = { weatherBeforeLayerId };
92
130
 
93
131
  this.map.on('load', () => {
94
- console.log("[MapManager] Map loaded. Applying initial theme:", defaultThemeName);
95
132
  applyStyleCustomizations(this.map, this.currentCustomizations);
96
133
  this.emit('style:applied', {
97
134
  themeName: this.currentThemeName,
@@ -103,7 +140,6 @@ export class MapManager extends EventEmitter {
103
140
  // The rest of the methods (setTheme, setLabelGroupVisibility, etc.) are correct and remain unchanged...
104
141
  setTheme(themeName) {
105
142
  if (!this.themes[themeName]) {
106
- console.error(`[MapManager] Theme "${themeName}" does not exist.`);
107
143
  return;
108
144
  }
109
145
 
@@ -141,7 +177,6 @@ export class MapManager extends EventEmitter {
141
177
  for (let i = 0; i < parts.length - 1; i++) {
142
178
  current = current[parts[i]];
143
179
  if (!current) {
144
- console.error(`Invalid label group key: ${groupKey}`);
145
180
  return;
146
181
  }
147
182
  }
@@ -152,9 +187,6 @@ export class MapManager extends EventEmitter {
152
187
 
153
188
  if (layerId && this.map.getLayer(layerId)) {
154
189
  this.map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
155
- console.log(`[MapManager] Set visibility for ${layerId} to ${visible}`);
156
- } else {
157
- console.warn(`[MapManager] Could not find layer for label group key: ${groupKey} (mapped to ${mapKey})`);
158
190
  }
159
191
  }
160
192