maplibre-gl-raster 0.4.0 → 0.5.0

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.
@@ -17263,10 +17263,18 @@ var WEB_MERCATOR_CODES = new Set([
17263
17263
  102100,
17264
17264
  102113
17265
17265
  ]);
17266
+ var GEOGRAPHIC_WGS84 = 4326;
17267
+ var EARTH_RADIUS = 6378137;
17268
+ var DEG2RAD = Math.PI / 180;
17266
17269
  var identity = (x, y) => [x, y];
17270
+ var geographicTo3857 = (lon, lat) => [EARTH_RADIUS * lon * DEG2RAD, EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + lat * DEG2RAD / 2))];
17271
+ var geographicFrom3857 = (x, y) => [x / EARTH_RADIUS / DEG2RAD, (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) / DEG2RAD];
17267
17272
  function isWebMercatorCrs(crs) {
17268
17273
  return typeof crs === "number" && WEB_MERCATOR_CODES.has(crs);
17269
17274
  }
17275
+ function isGeographicWgs84Crs(crs) {
17276
+ return crs === GEOGRAPHIC_WGS84;
17277
+ }
17270
17278
  /**
17271
17279
  * {@link COGLayer} that reprojects a Web-Mercator source with the identity
17272
17280
  * transform instead of a proj4 round-trip.
@@ -17287,23 +17295,33 @@ function isWebMercatorCrs(crs) {
17287
17295
  *
17288
17296
  * For a source already in EPSG:3857 the source→3857 transform *is* the
17289
17297
  * identity, so we substitute it directly: exact (no precision loss), wrap-free,
17290
- * and cheaper than proj4. The 4326 transforms are deliberately left untouched
17291
- * they are a genuine inverse mercator and back the geographic bounds and the
17292
- * globe view.
17298
+ * and cheaper than proj4. For a geographic EPSG:4326 source equally common
17299
+ * for global rasters, e.g. the CHIRPS precipitation COG the source→3857
17300
+ * transform is the spherical mercator forward, which we substitute with a
17301
+ * wrap-free implementation (`geographicTo3857`) so the antimeridian padding
17302
+ * columns no longer fold to the other hemisphere. Both substitutions only
17303
+ * touch the source↔3857 transforms; the 4326 bounds transforms are left
17304
+ * untouched — they back the geographic bounds and the globe view.
17293
17305
  *
17294
17306
  * The fix lives here rather than upstream because deck.gl-geotiff hard-codes the
17295
17307
  * projection setup inside `_parseGeoTIFF` and exposes no hook for it;
17296
17308
  * `_tilesetDescriptor()` is the one seam through which the built descriptor
17297
17309
  * flows to both tile traversal and per-tile rendering. Remove this once
17298
- * deck.gl-geotiff short-circuits same-CRS reprojection itself.
17310
+ * deck.gl-geotiff short-circuits same-CRS reprojection and clamps the
17311
+ * antimeridian overhang itself.
17299
17312
  */
17300
17313
  var WebMercatorCOGLayer = class extends COGLayer {
17301
17314
  static layerName = "WebMercatorCOGLayer";
17302
17315
  _tilesetDescriptor() {
17303
17316
  const descriptor = super._tilesetDescriptor();
17304
- if (descriptor && descriptor.projectTo3857 !== identity && isWebMercatorCrs(this.state.geotiff?.crs)) {
17317
+ if (!descriptor) return descriptor;
17318
+ const crs = this.state.geotiff?.crs;
17319
+ if (descriptor.projectTo3857 !== identity && isWebMercatorCrs(crs)) {
17305
17320
  descriptor.projectTo3857 = identity;
17306
17321
  descriptor.projectFrom3857 = identity;
17322
+ } else if (descriptor.projectTo3857 !== geographicTo3857 && isGeographicWgs84Crs(crs)) {
17323
+ descriptor.projectTo3857 = geographicTo3857;
17324
+ descriptor.projectFrom3857 = geographicFrom3857;
17307
17325
  }
17308
17326
  return descriptor;
17309
17327
  }
@@ -18067,7 +18085,50 @@ function deriveLayerName(source) {
18067
18085
  //#region src/lib/state/LayerManager.ts
18068
18086
  /** Default engine when none is configured: the deck.gl GPU pipeline. */
18069
18087
  var DEFAULT_ENGINE = "maplibre-gl-raster";
18070
- var getTileData = makeMultiBandTileLoader(Array.from({ length: 4 }, (_, i) => i + 1));
18088
+ /**
18089
+ * The band indexes a layer actually samples, deduped and sorted. Mirrors the
18090
+ * `requested` logic in render-pipeline's buildRenderTile: RGB samples the first
18091
+ * three entries (R, G, B); single-band / colormap / palette the first one. The
18092
+ * render pipeline looks textures up by band number, not slot order, so the
18093
+ * fetch order is irrelevant — sorting makes the set order-independent, so
18094
+ * reassigning RGB channels among the same bands does not force a refetch.
18095
+ * Always yields at least band 1 so a layer with an empty/invalid selection
18096
+ * still fetches something to draw.
18097
+ *
18098
+ * The sampled channels are sliced off **before** dedupe/sort so a state that
18099
+ * carries more entries than channels (e.g. `bands: [12, 1, 2, 3, 4]`) can't
18100
+ * sort-then-cap a band that a channel still samples (here the red channel's
18101
+ * 12) out of the fetched set. By construction the result is ≤ 3 entries — well
18102
+ * within the CompositeBands shader's {@link MAX_BAND_SLOTS} texture slots.
18103
+ */
18104
+ function fetchBandsFor(layer) {
18105
+ const bands = layer.state.bands;
18106
+ const sampled = layer.state.mode === "rgb" ? (bands && bands.length > 0 ? bands : [
18107
+ 1,
18108
+ 2,
18109
+ 3
18110
+ ]).slice(0, 3) : [bands?.[0] ?? 1];
18111
+ const unique = [...new Set(sampled.filter((b) => Number.isInteger(b) && b >= 1))].sort((a, b) => a - b);
18112
+ if (unique.length === 0) unique.push(1);
18113
+ return unique.slice(0, 4);
18114
+ }
18115
+ /**
18116
+ * The deck.gl layer id for a raster layer, with its fetched band set encoded.
18117
+ *
18118
+ * The tile loader only fetches {@link fetchBandsFor} (≤ MAX_BAND_SLOTS) bands,
18119
+ * so a single-band view of e.g. band 12 needs band 12 in the tile textures.
18120
+ * deck.gl's `TileLayer` does **not** refetch when `getTileData` changes — its
18121
+ * RasterTileLayer wrapper sets no `getTileData` updateTrigger, so a swapped
18122
+ * loader closure is silently ignored and the previously fetched bands stay
18123
+ * cached. The one reliable way to refetch is to remount the layer, which
18124
+ * deck.gl does when the layer id changes. Encoding the (sorted) band set in the
18125
+ * id therefore makes a band-selection change refetch the newly selected bands,
18126
+ * while leaving the id — and thus the tile cache — stable across opacity,
18127
+ * colormap, rescale and RGB-channel-reorder changes that don't alter the set.
18128
+ */
18129
+ function cogLayerId(layer, fetchBands) {
18130
+ return `${layer.id}#b${fetchBands.join("-")}`;
18131
+ }
18071
18132
  /** Uploads an embedded color table as a 2D-array texture for the Colormap
18072
18133
  * shader module. Unlike `createColormapTexture` (which uses linear filtering
18073
18134
  * for smooth continuous colormaps), palette lookups must be NEAREST-filtered:
@@ -18600,8 +18661,10 @@ var LayerManager = class {
18600
18661
  }
18601
18662
  _buildCogLayer(layer) {
18602
18663
  const renderTile = this._renderTileFor(layer);
18664
+ const fetchBands = fetchBandsFor(layer);
18665
+ const getTileData = makeMultiBandTileLoader(fetchBands);
18603
18666
  return new WebMercatorCOGLayer({
18604
- id: layer.id,
18667
+ id: cogLayerId(layer, fetchBands),
18605
18668
  geotiff: layer.geotiff,
18606
18669
  opacity: layer.state.opacity,
18607
18670
  getTileData,
@@ -19548,7 +19611,7 @@ var SettingsSection = class {
19548
19611
  }
19549
19612
  const state = layer.state;
19550
19613
  const mode = state.mode;
19551
- const bandOptions = Array.from({ length: Math.min(layer.bandCount ?? 4, 4) }, (_, i) => i + 1);
19614
+ const bandOptions = Array.from({ length: Math.max(1, layer.bandCount ?? 1) }, (_, i) => i + 1);
19552
19615
  const paletteActive = mode === "single" && state.colormap === "palette" && layer.palette !== null;
19553
19616
  this._body.appendChild(this._buildModeField(state, mode, bandOptions));
19554
19617
  this._body.appendChild(this._buildBandsField(layer, state, mode, bandOptions));
@@ -20897,4 +20960,4 @@ var RasterControl = class {
20897
20960
  //#endregion
20898
20961
  export { computeAutoStats as C, createResilientEpsgResolver as E, MAX_SAMPLE_TILES as S, readBandNames as T, COLORMAP_ROW_COUNT as _, loadColormapSprite as a, autoRangeFor as b, classNames as c, generateId as d, throttle as f, COLORMAP_OPTIONS as g, COLORMAP_NAMES as h, isKnownColormap as i, debounce as l, COLORMAP_DISPLAY_NAMES as m, Colorbar as n, sampleColormapStops as o, loadGeoTIFF as p, PALETTE_COLORMAP as r, clamp as s, RasterControl as t, formatNumericValue as u, colormapDisplayName as v, percentileFromHistogram as w, statsForBand as x, colormaps_default as y };
20899
20962
 
20900
- //# sourceMappingURL=RasterControl-90FXg5Gi.js.map
20963
+ //# sourceMappingURL=RasterControl-vzjXiCsa.js.map