astro-viewer 3.7.0 → 3.9.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.
Files changed (56) hide show
  1. package/README.md +325 -222
  2. package/dist/astroviewer.cjs +654 -108
  3. package/dist/astroviewer.cjs.map +1 -1
  4. package/dist/astroviewer.js +655 -108
  5. package/dist/astroviewer.js.map +1 -1
  6. package/dist/astroviewer.min.js +1 -1
  7. package/dist/astroviewer.min.js.map +1 -1
  8. package/lib-esm/AstroSphere.d.ts +13 -1
  9. package/lib-esm/AstroSphere.d.ts.map +1 -1
  10. package/lib-esm/AstroSphere.js +94 -5
  11. package/lib-esm/AstroSphere.js.map +1 -1
  12. package/lib-esm/AstroViewer.d.ts +11 -0
  13. package/lib-esm/AstroViewer.d.ts.map +1 -1
  14. package/lib-esm/AstroViewer.js +37 -2
  15. package/lib-esm/AstroViewer.js.map +1 -1
  16. package/lib-esm/index.d.ts +1 -0
  17. package/lib-esm/index.d.ts.map +1 -1
  18. package/lib-esm/index.js +0 -1
  19. package/lib-esm/index.js.map +1 -1
  20. package/lib-esm/model/grid/HealpixGrid.d.ts.map +1 -1
  21. package/lib-esm/model/grid/HealpixGrid.js +7 -1
  22. package/lib-esm/model/grid/HealpixGrid.js.map +1 -1
  23. package/lib-esm/model/hips/AllSky.d.ts +0 -1
  24. package/lib-esm/model/hips/AllSky.d.ts.map +1 -1
  25. package/lib-esm/model/hips/AllSky.js +11 -32
  26. package/lib-esm/model/hips/AllSky.js.map +1 -1
  27. package/lib-esm/model/hips/AncestorTile.d.ts.map +1 -1
  28. package/lib-esm/model/hips/AncestorTile.js +10 -27
  29. package/lib-esm/model/hips/AncestorTile.js.map +1 -1
  30. package/lib-esm/model/hips/FitsTileLoader.d.ts +4 -0
  31. package/lib-esm/model/hips/FitsTileLoader.d.ts.map +1 -1
  32. package/lib-esm/model/hips/FitsTileLoader.js +39 -1
  33. package/lib-esm/model/hips/FitsTileLoader.js.map +1 -1
  34. package/lib-esm/model/hips/HiPS.d.ts +31 -0
  35. package/lib-esm/model/hips/HiPS.d.ts.map +1 -1
  36. package/lib-esm/model/hips/HiPS.js +193 -0
  37. package/lib-esm/model/hips/HiPS.js.map +1 -1
  38. package/lib-esm/model/hips/HiPSCoverage.d.ts +6 -1
  39. package/lib-esm/model/hips/HiPSCoverage.d.ts.map +1 -1
  40. package/lib-esm/model/hips/HiPSCoverage.js +20 -2
  41. package/lib-esm/model/hips/HiPSCoverage.js.map +1 -1
  42. package/lib-esm/model/hips/Tile.d.ts +0 -1
  43. package/lib-esm/model/hips/Tile.d.ts.map +1 -1
  44. package/lib-esm/model/hips/Tile.js +9 -27
  45. package/lib-esm/model/hips/Tile.js.map +1 -1
  46. package/lib-esm/model/hips/TileBuffer.d.ts.map +1 -1
  47. package/lib-esm/model/hips/TileBuffer.js +6 -0
  48. package/lib-esm/model/hips/TileBuffer.js.map +1 -1
  49. package/lib-esm/shader/HiPSShaderProgram.d.ts +7 -1
  50. package/lib-esm/shader/HiPSShaderProgram.d.ts.map +1 -1
  51. package/lib-esm/shader/HiPSShaderProgram.js +23 -1
  52. package/lib-esm/shader/HiPSShaderProgram.js.map +1 -1
  53. package/lib-esm/shader/ShaderManager.d.ts.map +1 -1
  54. package/lib-esm/shader/ShaderManager.js +46 -10
  55. package/lib-esm/shader/ShaderManager.js.map +1 -1
  56. package/package.json +1 -1
@@ -3627,6 +3627,7 @@ class AstroSphere {
3627
3627
  pointerDownY = null;
3628
3628
  pointerDownAt = 0;
3629
3629
  _activeHiPS = null;
3630
+ _activeHiPSLayers = [];
3630
3631
  _activeXYZ2 = null;
3631
3632
  _activeMeshHiPS = null;
3632
3633
  _activeBaseLayer = null;
@@ -4129,12 +4130,84 @@ class AstroSphere {
4129
4130
  }
4130
4131
  this._activeHiPS.changeFormat(format);
4131
4132
  }
4133
+ setHiPSFITSScaleFunction(scaleFunction, scaleParam) {
4134
+ if (!this._activeHiPS) {
4135
+ throw new Error("No active HiPS.");
4136
+ }
4137
+ this._activeHiPS.setFITSScaleFunction(scaleFunction, scaleParam);
4138
+ }
4139
+ getActiveHiPSFITSStretch() {
4140
+ return this._activeHiPS?.fitsStretch ?? null;
4141
+ }
4142
+ setHiPSFITSRangeMode(rangeMode) {
4143
+ if (!this._activeHiPS) {
4144
+ throw new Error("No active HiPS.");
4145
+ }
4146
+ this._activeHiPS.setFITSRangeMode(rangeMode);
4147
+ }
4148
+ createHiPS(hipsDescriptor) {
4149
+ return new HiPS_js_1.HiPS(1, [0.0, 0.0, 0.0], 0, 0, hipsDescriptor, this._webgl, this._healpixGrid);
4150
+ }
4132
4151
  activateHiPS(hipsDescriptor) {
4133
- if (this._activeHiPS) {
4134
- this._healpixGrid.visibleTilesManager.tileBuffer.removeHiPS(this._activeHiPS);
4152
+ const tileBuffer = this._healpixGrid.visibleTilesManager.tileBuffer;
4153
+ for (const hips of this._activeHiPSLayers) {
4154
+ tileBuffer.removeHiPS(hips);
4155
+ }
4156
+ this._activeHiPSLayers = [];
4157
+ const hips = this.createHiPS(hipsDescriptor);
4158
+ this._activeHiPSLayers.push(hips);
4159
+ this._activeHiPS = hips;
4160
+ this._activeBaseLayer = "hips";
4161
+ return hips;
4162
+ }
4163
+ setHiPSOpacity(hips, opacity) {
4164
+ if (!this._activeHiPSLayers.includes(hips)) {
4165
+ throw new Error("HiPS layer is not active in this AstroSphere.");
4166
+ }
4167
+ hips.setOpacity(opacity);
4168
+ }
4169
+ addHiPS(hipsDescriptor) {
4170
+ const normalizeURL = (url) => String(url).replace(/\/+$/, "");
4171
+ const descriptorURL = normalizeURL(hipsDescriptor.url);
4172
+ const existing = this._activeHiPSLayers.find((hips) => normalizeURL(hips.baseURL) === descriptorURL);
4173
+ if (existing) {
4174
+ throw new Error(`HiPS already active: ${String(hipsDescriptor.url)}`);
4135
4175
  }
4136
- this._activeHiPS = new HiPS_js_1.HiPS(1, [0.0, 0.0, 0.0], 0, 0, hipsDescriptor, this._webgl, this._healpixGrid);
4176
+ const hips = this.createHiPS(hipsDescriptor);
4177
+ this._activeHiPSLayers.push(hips);
4178
+ this._activeHiPS = hips;
4137
4179
  this._activeBaseLayer = "hips";
4180
+ return hips;
4181
+ }
4182
+ removeHiPS(hips) {
4183
+ const index = this._activeHiPSLayers.indexOf(hips);
4184
+ if (index === -1) {
4185
+ return;
4186
+ }
4187
+ this._healpixGrid.visibleTilesManager.tileBuffer.removeHiPS(hips);
4188
+ this._activeHiPSLayers.splice(index, 1);
4189
+ if (this._activeHiPSLayers.length === 0) {
4190
+ this._activeHiPS = null;
4191
+ if (this._activeBaseLayer === "hips") {
4192
+ this._activeBaseLayer = null;
4193
+ }
4194
+ return;
4195
+ }
4196
+ if (this._activeHiPS === hips) {
4197
+ this._activeHiPS =
4198
+ this._activeHiPSLayers[this._activeHiPSLayers.length - 1];
4199
+ }
4200
+ }
4201
+ removeAllHiPS() {
4202
+ const tileBuffer = this._healpixGrid.visibleTilesManager.tileBuffer;
4203
+ for (const hips of this._activeHiPSLayers) {
4204
+ tileBuffer.removeHiPS(hips);
4205
+ }
4206
+ this._activeHiPSLayers = [];
4207
+ this._activeHiPS = null;
4208
+ if (this._activeBaseLayer === "hips") {
4209
+ this._activeBaseLayer = null;
4210
+ }
4138
4211
  }
4139
4212
  activateXYZ(config) {
4140
4213
  this.activateXYZ2(new XYZMapDescriptor_js_1.XYZMapDescriptor(config.name ?? "XYZ Earth2 Layer", config.urlTemplate, config.minZoom ?? 0, config.maxZoom ?? 8, config.segmentsPerSide ?? 48, config.maxCachedTiles ?? 384, 8, config.urlResolver));
@@ -4367,6 +4440,16 @@ class AstroSphere {
4367
4440
  get activeHiPS() {
4368
4441
  return this._activeHiPS;
4369
4442
  }
4443
+ get activeHiPSLayers() {
4444
+ return this._activeHiPSLayers;
4445
+ }
4446
+ setActiveHiPS(hips) {
4447
+ if (!this._activeHiPSLayers.includes(hips)) {
4448
+ throw new Error("HiPS layer is not active in this AstroSphere.");
4449
+ }
4450
+ this._activeHiPS = hips;
4451
+ this._activeBaseLayer = "hips";
4452
+ }
4370
4453
  get activeXYZ() {
4371
4454
  return this._activeXYZ2;
4372
4455
  }
@@ -4526,8 +4609,9 @@ class AstroSphere {
4526
4609
  this._webgl.enable(this._webgl.CULL_FACE);
4527
4610
  this._webgl.cullFace(Global_js_1.default.insideSphere ? this._webgl.FRONT : this._webgl.BACK);
4528
4611
  this._webgl.blendFunc(this._webgl.SRC_ALPHA, this._webgl.ONE_MINUS_SRC_ALPHA);
4529
- if (this._activeBaseLayer === "hips" && this._activeHiPS) {
4530
- const visibleOrder = Math.min(this._healpixGrid.visibleorder, this._activeHiPS.maxOrder);
4612
+ if (this._activeBaseLayer === "hips" && this._activeHiPSLayers.length > 0) {
4613
+ const maxHiPSOrder = Math.max(...this._activeHiPSLayers.map((hips) => hips.maxOrder));
4614
+ const visibleOrder = Math.min(this._healpixGrid.visibleorder, maxHiPSOrder);
4531
4615
  this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
4532
4616
  }
4533
4617
  if (this._activeBaseLayer === "meships" && this._activeMeshHiPS) {
@@ -4552,6 +4636,11 @@ class AstroSphere {
4552
4636
  cameraMoving: cameraMovingForGrid,
4553
4637
  };
4554
4638
  if (this._activeBaseLayer === "hips") {
4639
+ for (const hips of this._activeHiPSLayers) {
4640
+ if (hips !== this._activeHiPS) {
4641
+ hips.draw(skyEntityDrawInput);
4642
+ }
4643
+ }
4555
4644
  this._activeHiPS?.draw(skyEntityDrawInput);
4556
4645
  }
4557
4646
  if (this._activeBaseLayer === "xyz") {
@@ -4886,21 +4975,56 @@ class AstroViewer {
4886
4975
  this.astroSphere.activateMeshHiPS(desc);
4887
4976
  return desc.name;
4888
4977
  }
4889
- // changeColorMap(hips: HiPS, colorMapName: ColorMapName) {
4978
+ setActiveHiPS(hips) {
4979
+ this.astroSphere.setActiveHiPS(hips);
4980
+ }
4981
+ setHiPSOpacity(hips, opacity) {
4982
+ this.astroSphere.setHiPSOpacity(hips, opacity);
4983
+ }
4890
4984
  changeColorMap(colorMapName) {
4891
4985
  const colorMap = ColorMaps_js_1.default[colorMapName];
4892
- // hips.changeColorMap(colorMap)
4893
4986
  this.astroSphere.changeColorMap(colorMap);
4894
4987
  }
4895
4988
  changeCustomColorMap(colorMap) {
4896
4989
  this.astroSphere.changeColorMap(colorMap);
4897
4990
  }
4991
+ addHiPS(hipsDescriptor) {
4992
+ return this.astroSphere.addHiPS(hipsDescriptor);
4993
+ }
4994
+ async addHiPSFromUrl(baseUrl) {
4995
+ const hipsUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
4996
+ const resp = await fetch(hipsUrl + "properties");
4997
+ if (!resp.ok) {
4998
+ throw new Error(`HTTP ${resp.status} fetching properties`);
4999
+ }
5000
+ const propsText = await resp.text();
5001
+ const desc = new HiPSDescriptor_js_1.HiPSDescriptor(propsText, hipsUrl);
5002
+ return this.astroSphere.addHiPS(desc);
5003
+ }
5004
+ removeHiPS(hips) {
5005
+ this.astroSphere.removeHiPS(hips);
5006
+ }
5007
+ removeAllHiPS() {
5008
+ this.astroSphere.removeAllHiPS();
5009
+ }
5010
+ getActiveHiPSLayers() {
5011
+ return this.astroSphere.activeHiPSLayers;
5012
+ }
4898
5013
  getActiveHiPS() {
4899
5014
  return this.astroSphere.activeHiPS;
4900
5015
  }
4901
5016
  changeHiPSFormat(format) {
4902
5017
  this.astroSphere.changeHiPSFormat(format);
4903
5018
  }
5019
+ setHiPSFITSScaleFunction(scaleFunction, scaleParam) {
5020
+ this.astroSphere.setHiPSFITSScaleFunction(scaleFunction, scaleParam);
5021
+ }
5022
+ getActiveHiPSFITSStretch() {
5023
+ return this.astroSphere.getActiveHiPSFITSStretch();
5024
+ }
5025
+ setHiPSFITSRangeMode(rangeMode) {
5026
+ this.astroSphere.setHiPSFITSRangeMode(rangeMode);
5027
+ }
4904
5028
  getActiveHiPSFormats() {
4905
5029
  return this.astroSphere.activeHiPS?.availableFormats ?? [];
4906
5030
  }
@@ -5767,7 +5891,6 @@ var Source_js_1 = __webpack_require__(146);
5767
5891
  Object.defineProperty(exports, "Source", ({ enumerable: true, get: function () { return Source_js_1.Source; } }));
5768
5892
  var Footprint_js_1 = __webpack_require__(2475);
5769
5893
  Object.defineProperty(exports, "Footprint", ({ enumerable: true, get: function () { return Footprint_js_1.Footprint; } }));
5770
- console.log('astroviewer UMD loaded');
5771
5894
 
5772
5895
 
5773
5896
  /***/ },
@@ -11533,8 +11656,14 @@ class HealpixGrid extends AbstractSkyEntity_js_1.AbstractSkyEntity {
11533
11656
  // FIX: pass model & pMatrix to match FoVUtils TS signature
11534
11657
  const center = FoVUtils_js_1.FoVUtils.getCenterJ2000(gl.canvas, this, this._webgl, camera, pMatrix);
11535
11658
  const fovMin = (this.getMinFoV() * Math.PI) / 180 / 2;
11659
+ const visibleHealpix = Global_js_1.default.getHealpix(this._visibleorder);
11660
+ const maxPixel = visibleHealpix.getNPix();
11536
11661
  for (let p = 0; p < pixels.length; p++) {
11537
- const pixCenter = Global_js_1.default.getHealpix(this._visibleorder).pix2vec(pixels[p]);
11662
+ const pixel = pixels[p];
11663
+ if (!Number.isInteger(pixel) || pixel < 0 || pixel >= maxPixel) {
11664
+ continue;
11665
+ }
11666
+ const pixCenter = visibleHealpix.pix2vec(pixel);
11538
11667
  // const pixCenter = (global.getHealpix(global.order).pix2vec(pixels[p]) as BoundVec);
11539
11668
  const point = new Point_js_1.Point({ x: pixCenter.x, y: pixCenter.y, z: pixCenter.z }, CoordsType_js_1.CoordsType.CARTESIAN);
11540
11669
  const distance = GeomUtils_js_1.default.orthodromicDistance(center, point);
@@ -11915,7 +12044,7 @@ class AllSky {
11915
12044
  _baseurl;
11916
12045
  _isGalacticHips;
11917
12046
  _order = 3;
11918
- opacity = 1.0;
12047
+ // public opacity = 1.0;
11919
12048
  _hipsShaderIndex = 0;
11920
12049
  _texture = null;
11921
12050
  _image;
@@ -11962,6 +12091,7 @@ class AllSky {
11962
12091
  async loadFits() {
11963
12092
  try {
11964
12093
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
12094
+ this._hips.observeFITSDataRange(this._fitsTile);
11965
12095
  this.textureLoaded();
11966
12096
  this.initModelBuffer();
11967
12097
  this._textureLoaded = true;
@@ -12006,30 +12136,7 @@ class AllSky {
12006
12136
  }
12007
12137
  }
12008
12138
  getFitsDataRange() {
12009
- const hipsRange = this._hips.dataRange;
12010
- if (hipsRange.min !== undefined &&
12011
- hipsRange.max !== undefined &&
12012
- Number.isFinite(hipsRange.min) &&
12013
- Number.isFinite(hipsRange.max) &&
12014
- hipsRange.max > hipsRange.min) {
12015
- return {
12016
- min: hipsRange.min,
12017
- max: hipsRange.max,
12018
- };
12019
- }
12020
- const fits = this._fitsTile;
12021
- if (fits &&
12022
- fits.dataMin !== null &&
12023
- fits.dataMax !== null &&
12024
- Number.isFinite(fits.dataMin) &&
12025
- Number.isFinite(fits.dataMax) &&
12026
- fits.dataMax > fits.dataMin) {
12027
- return {
12028
- min: fits.dataMin,
12029
- max: fits.dataMax,
12030
- };
12031
- }
12032
- return null;
12139
+ return this._hips.getFITSDisplayRange(this._fitsTile);
12033
12140
  }
12034
12141
  initModelBuffer() {
12035
12142
  const gl = this._webgl;
@@ -12143,7 +12250,7 @@ class AllSky {
12143
12250
  console.error(`[AllSky] No display range available for FITS atlas ${this._texurl}`);
12144
12251
  return false;
12145
12252
  }
12146
- this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
12253
+ this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
12147
12254
  }
12148
12255
  else {
12149
12256
  this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -12156,9 +12263,10 @@ class AllSky {
12156
12263
  gl.activeTexture(gl.TEXTURE0 + this._hipsShaderIndex);
12157
12264
  gl.bindTexture(gl.TEXTURE_2D, this._texture);
12158
12265
  const factorLocation = this._hipsShaderProgram.locations.textureAlpha[this._hipsShaderIndex];
12159
- if (factorLocation) {
12160
- gl.uniform1f(factorLocation, this.opacity);
12266
+ if (factorLocation !== null) {
12267
+ gl.uniform1f(factorLocation, 1.0);
12161
12268
  }
12269
+ this._hipsShaderProgram.setLayerOpacity(this._hips.opacity);
12162
12270
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexPositionBuffer);
12163
12271
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.vertexIndexBuffer);
12164
12272
  gl.vertexAttribPointer(
@@ -12186,13 +12294,13 @@ class AllSky {
12186
12294
  const allSkyTiles2Skip = [];
12187
12295
  for (let i = 0; i < visibleTiles.length; i++) {
12188
12296
  const tileno = visibleTiles[i];
12297
+ if (!this._hips.intersectsCoverage(childrenOrder, tileno)) {
12298
+ allSkyTiles2Skip.push(tileno);
12299
+ continue;
12300
+ }
12189
12301
  const childTile = this._isGalacticHips
12190
12302
  ? this._tileBuffer.getGalTile(tileno, childrenOrder, this._hips)
12191
12303
  : this._tileBuffer.getTile(tileno, childrenOrder, this._hips);
12192
- // const childTile = this._isGalacticHips
12193
- // ? newTileBuffer.getGalTile(tileno, childrenOrder, this._hips)
12194
- // : newTileBuffer.getTile(tileno, childrenOrder, this._hips)
12195
- // childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx)
12196
12304
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, this._hipsShaderProgram);
12197
12305
  if (childTile.getReadyState()) {
12198
12306
  allSkyTiles2Skip.push(tileno);
@@ -12279,6 +12387,7 @@ class AncestorTile {
12279
12387
  async loadFits() {
12280
12388
  try {
12281
12389
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
12390
+ this._hips.observeFITSDataRange(this._fitsTile);
12282
12391
  this.textureLoaded();
12283
12392
  const fits = this._fitsTile;
12284
12393
  if (!fits) {
@@ -12336,30 +12445,7 @@ class AncestorTile {
12336
12445
  }
12337
12446
  }
12338
12447
  getFitsDataRange() {
12339
- const hipsRange = this._hips.dataRange;
12340
- if (hipsRange.min !== undefined &&
12341
- hipsRange.max !== undefined &&
12342
- Number.isFinite(hipsRange.min) &&
12343
- Number.isFinite(hipsRange.max) &&
12344
- hipsRange.max > hipsRange.min) {
12345
- return {
12346
- min: hipsRange.min,
12347
- max: hipsRange.max,
12348
- };
12349
- }
12350
- const fits = this._fitsTile;
12351
- if (fits &&
12352
- fits.dataMin !== null &&
12353
- fits.dataMax !== null &&
12354
- Number.isFinite(fits.dataMin) &&
12355
- Number.isFinite(fits.dataMax) &&
12356
- fits.dataMax > fits.dataMin) {
12357
- return {
12358
- min: fits.dataMin,
12359
- max: fits.dataMax,
12360
- };
12361
- }
12362
- return null;
12448
+ return this._hips.getFITSDisplayRange(this._fitsTile);
12363
12449
  }
12364
12450
  initModelBuffer() {
12365
12451
  const gl = this._webgl;
@@ -12459,7 +12545,7 @@ class AncestorTile {
12459
12545
  console.error(`[AncestorTile] No display range available for FITS tile ${this._texurl}`);
12460
12546
  return false;
12461
12547
  }
12462
- this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
12548
+ this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
12463
12549
  }
12464
12550
  else {
12465
12551
  this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -12492,16 +12578,21 @@ class AncestorTile {
12492
12578
  return;
12493
12579
  for (let c = 0; c < 4; c++) {
12494
12580
  const childTileNo = (this._tileno << 2) + c;
12581
+ if (!this._hips.intersectsCoverage(childrenOrder, childTileNo)) {
12582
+ continue;
12583
+ }
12495
12584
  const visibleChildren = visibleTilesMap.get(childrenOrder);
12496
12585
  if (visibleChildren.includes(childTileNo)) {
12586
+ if (!this._hips.intersectsCoverage(childrenOrder, childTileNo)) {
12587
+ quadrantsToDraw.delete(c);
12588
+ continue;
12589
+ }
12497
12590
  const childTile = this._isGalacticHips
12498
12591
  ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
12499
12592
  : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
12500
- // childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx)
12501
12593
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, this._hipsShaderProgram);
12502
12594
  if (childTile.getReadyState()) {
12503
12595
  quadrantsToDraw.delete(c);
12504
- // quadrantsToDraw.delete((childTile as any)._tileno - (this._tileno << 2))
12505
12596
  }
12506
12597
  }
12507
12598
  }
@@ -12531,6 +12622,9 @@ var __webpack_unused_export__;
12531
12622
  __webpack_unused_export__ = ({ value: true });
12532
12623
  exports.loadFitsTile = loadFitsTile;
12533
12624
  const jsfitsio_1 = __webpack_require__(4971);
12625
+ const ROBUST_SAMPLE_COUNT = 8192;
12626
+ const ROBUST_LOW_PERCENTILE = 0.005;
12627
+ const ROBUST_HIGH_PERCENTILE = 0.995;
12534
12628
  async function loadFitsTile(url) {
12535
12629
  const fitsFile = await jsfitsio_1.FITSParser.loadFITSFile(url);
12536
12630
  if (!fitsFile?.primaryHDU) {
@@ -12560,22 +12654,57 @@ async function loadFitsTile(url) {
12560
12654
  const dataMin = getNumericHeaderValue(image.header, jsfitsio_1.FITSHeaderManager.DATAMIN);
12561
12655
  const dataMax = getNumericHeaderValue(image.header, jsfitsio_1.FITSHeaderManager.DATAMAX);
12562
12656
  const pixels = new Float32Array(data.length);
12657
+ let physicalMin = null;
12658
+ let physicalMax = null;
12659
+ const robustSamples = [];
12660
+ const sampleStep = Math.max(1, Math.floor(data.length / ROBUST_SAMPLE_COUNT));
12563
12661
  for (let i = 0; i < data.length; i++) {
12564
12662
  const rawValue = data[i];
12565
12663
  if (!Number.isFinite(rawValue) || (blank !== null && rawValue === blank)) {
12566
12664
  pixels[i] = Number.NaN;
12567
12665
  continue;
12568
12666
  }
12569
- pixels[i] = rawValue * bscale + bzero;
12667
+ const physicalValue = rawValue * bscale + bzero;
12668
+ pixels[i] = physicalValue;
12669
+ if (Number.isFinite(physicalValue)) {
12670
+ physicalMin =
12671
+ physicalMin === null ? physicalValue : Math.min(physicalMin, physicalValue);
12672
+ physicalMax =
12673
+ physicalMax === null ? physicalValue : Math.max(physicalMax, physicalValue);
12674
+ if (i % sampleStep === 0) {
12675
+ robustSamples.push(physicalValue);
12676
+ }
12677
+ }
12570
12678
  }
12679
+ const robustRange = computeRobustRange(robustSamples);
12571
12680
  return {
12572
12681
  width,
12573
12682
  height,
12574
12683
  pixels,
12575
12684
  dataMin,
12576
12685
  dataMax,
12686
+ physicalMin,
12687
+ physicalMax,
12688
+ robustMin: robustRange?.min ?? null,
12689
+ robustMax: robustRange?.max ?? null,
12577
12690
  };
12578
12691
  }
12692
+ function computeRobustRange(samples) {
12693
+ if (samples.length === 0) {
12694
+ return null;
12695
+ }
12696
+ samples.sort((a, b) => a - b);
12697
+ const min = percentile(samples, ROBUST_LOW_PERCENTILE);
12698
+ const max = percentile(samples, ROBUST_HIGH_PERCENTILE);
12699
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
12700
+ return null;
12701
+ }
12702
+ return { min, max };
12703
+ }
12704
+ function percentile(sortedValues, p) {
12705
+ const index = Math.min(sortedValues.length - 1, Math.max(0, Math.round((sortedValues.length - 1) * p)));
12706
+ return sortedValues[index];
12707
+ }
12579
12708
  function getNumericHeaderValue(header, key) {
12580
12709
  const item = header.findById(key);
12581
12710
  if (!item) {
@@ -12778,10 +12907,29 @@ const FoVHelper_js_1 = __webpack_require__(229);
12778
12907
  const ColorMaps_js_1 = __importDefault(__webpack_require__(619));
12779
12908
  const AncestorTile_js_1 = __importDefault(__webpack_require__(2885));
12780
12909
  const AllSky_js_1 = __importDefault(__webpack_require__(2368));
12910
+ const HiPSCoverage_js_1 = __webpack_require__(9912);
12911
+ const FITS_SCALE_FUNCTION_INDEX = {
12912
+ linear: 0,
12913
+ sqrt: 1,
12914
+ log: 2,
12915
+ asinh: 3,
12916
+ gamma: 4,
12917
+ };
12918
+ const DEFAULT_FITS_SCALE_PARAM = {
12919
+ linear: 1,
12920
+ sqrt: 1,
12921
+ log: 100,
12922
+ asinh: 10,
12923
+ gamma: 0.5,
12924
+ };
12925
+ const MAX_ROBUST_TILE_RANGES = 512;
12781
12926
  class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12782
12927
  _ancestorTiles;
12783
12928
  _allSkyTile;
12784
12929
  _descriptor;
12930
+ _coverage;
12931
+ _coverageResolved = false;
12932
+ _opacity = 1.0;
12785
12933
  _format;
12786
12934
  _baseurl;
12787
12935
  _maxorder;
@@ -12791,6 +12939,13 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12791
12939
  samplerIdx = 0;
12792
12940
  colorMapIdx = 0;
12793
12941
  colorMap = ColorMaps_js_1.default["native"];
12942
+ _fitsStretch = {
12943
+ scaleFunction: "linear",
12944
+ scaleFunctionIndex: FITS_SCALE_FUNCTION_INDEX.linear,
12945
+ scaleParam: DEFAULT_FITS_SCALE_PARAM.linear,
12946
+ rangeMode: "robust",
12947
+ };
12948
+ _robustFITSRanges = [];
12794
12949
  _healpixGrid;
12795
12950
  // exposed read-only helpers
12796
12951
  get maxOrder() {
@@ -12820,6 +12975,17 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12820
12975
  this._baseurl = descriptor.url;
12821
12976
  this._maxorder = descriptor.maxOrder;
12822
12977
  this._minorder = descriptor.minOrder;
12978
+ this._coverage = new HiPSCoverage_js_1.HiPSCoverage();
12979
+ void this._coverage
12980
+ .load(this._baseurl)
12981
+ .then(() => {
12982
+ this._coverageResolved = true;
12983
+ })
12984
+ .catch((error) => {
12985
+ console.warn(`[HiPS] Coverage unavailable for ${this._baseurl}; continuing without MOC filtering.`, error);
12986
+ this._coverage.clear();
12987
+ this._coverageResolved = true;
12988
+ });
12823
12989
  if (this.isGalacticHips) {
12824
12990
  this._healpixGrid.visibleTilesManager.tileBuffer.addGalHiPS(this);
12825
12991
  }
@@ -12842,6 +13008,31 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12842
13008
  this._allSky = true;
12843
13009
  this.initBaseTiles();
12844
13010
  }
13011
+ setOpacity(opacity) {
13012
+ if (!Number.isFinite(opacity)) {
13013
+ throw new Error(`Invalid HiPS opacity: ${opacity}`);
13014
+ }
13015
+ this._opacity = Math.min(1, Math.max(0, opacity));
13016
+ }
13017
+ get opacity() {
13018
+ return this._opacity;
13019
+ }
13020
+ intersectsCoverage(order, pixel) {
13021
+ /*
13022
+ * While Moc.fits is being resolved, do not start tile requests.
13023
+ *
13024
+ * Once loading has completed:
13025
+ * - valid MOC -> filter using coverage
13026
+ * - unavailable MOC -> HiPSCoverage is cleared and normal loading resumes
13027
+ */
13028
+ if (!this._coverageResolved) {
13029
+ return false;
13030
+ }
13031
+ return this._coverage.intersectsTile(order, pixel);
13032
+ }
13033
+ get coverageLoaded() {
13034
+ return this._coverage.loaded;
13035
+ }
12845
13036
  initBaseTiles() {
12846
13037
  this._ancestorTiles = [];
12847
13038
  this._allSkyTile = null;
@@ -12990,6 +13181,137 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12990
13181
  get dataRange() {
12991
13182
  return this._descriptor.dataRange;
12992
13183
  }
13184
+ get fitsStretch() {
13185
+ return this._fitsStretch;
13186
+ }
13187
+ setFITSScaleFunction(scaleFunction, scaleParam) {
13188
+ const scaleFunctionIndex = FITS_SCALE_FUNCTION_INDEX[scaleFunction];
13189
+ if (scaleFunctionIndex === undefined) {
13190
+ throw new Error(`Unsupported FITS scale function: ${scaleFunction}`);
13191
+ }
13192
+ const defaultParam = DEFAULT_FITS_SCALE_PARAM[scaleFunction];
13193
+ const param = scaleParam ?? defaultParam;
13194
+ if (!Number.isFinite(param) || param <= 0) {
13195
+ throw new Error(`Invalid FITS scale parameter: ${param}`);
13196
+ }
13197
+ this._fitsStretch = {
13198
+ ...this._fitsStretch,
13199
+ scaleFunction,
13200
+ scaleFunctionIndex,
13201
+ scaleParam: param,
13202
+ };
13203
+ }
13204
+ setFITSRangeMode(rangeMode) {
13205
+ if (rangeMode !== "robust" && rangeMode !== "hips" && rangeMode !== "tile") {
13206
+ throw new Error(`Unsupported FITS range mode: ${rangeMode}`);
13207
+ }
13208
+ this._fitsStretch = {
13209
+ ...this._fitsStretch,
13210
+ rangeMode,
13211
+ };
13212
+ }
13213
+ observeFITSDataRange(fits) {
13214
+ if (fits.robustMin === null ||
13215
+ fits.robustMax === null ||
13216
+ !Number.isFinite(fits.robustMin) ||
13217
+ !Number.isFinite(fits.robustMax) ||
13218
+ fits.robustMax <= fits.robustMin) {
13219
+ return;
13220
+ }
13221
+ this._robustFITSRanges.push({
13222
+ min: fits.robustMin,
13223
+ max: fits.robustMax,
13224
+ });
13225
+ if (this._robustFITSRanges.length > MAX_ROBUST_TILE_RANGES) {
13226
+ this._robustFITSRanges.shift();
13227
+ }
13228
+ }
13229
+ getFITSDisplayRange(fits) {
13230
+ const hipsRange = this.getHiPSDataRange();
13231
+ if (this._fitsStretch.rangeMode === "hips" && hipsRange) {
13232
+ return hipsRange;
13233
+ }
13234
+ if (this._fitsStretch.rangeMode === "robust") {
13235
+ const robustRange = this.getRobustFITSRange() ?? this.getTileRobustRange(fits);
13236
+ if (robustRange) {
13237
+ return robustRange;
13238
+ }
13239
+ }
13240
+ const tileRange = this.getTilePhysicalRange(fits) ?? this.getTileHeaderRange(fits);
13241
+ if (tileRange) {
13242
+ return tileRange;
13243
+ }
13244
+ return hipsRange;
13245
+ }
13246
+ getHiPSDataRange() {
13247
+ const hipsRange = this.dataRange;
13248
+ if (hipsRange.min !== undefined &&
13249
+ hipsRange.max !== undefined &&
13250
+ Number.isFinite(hipsRange.min) &&
13251
+ Number.isFinite(hipsRange.max) &&
13252
+ hipsRange.max > hipsRange.min) {
13253
+ return {
13254
+ min: hipsRange.min,
13255
+ max: hipsRange.max,
13256
+ };
13257
+ }
13258
+ return null;
13259
+ }
13260
+ getRobustFITSRange() {
13261
+ if (this._robustFITSRanges.length === 0) {
13262
+ return null;
13263
+ }
13264
+ const lows = this._robustFITSRanges.map((range) => range.min).sort((a, b) => a - b);
13265
+ const highs = this._robustFITSRanges.map((range) => range.max).sort((a, b) => a - b);
13266
+ const min = lows[Math.floor((lows.length - 1) * 0.1)];
13267
+ const max = highs[Math.ceil((highs.length - 1) * 0.9)];
13268
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
13269
+ return null;
13270
+ }
13271
+ return { min, max };
13272
+ }
13273
+ getTileRobustRange(fits) {
13274
+ if (fits &&
13275
+ fits.robustMin !== null &&
13276
+ fits.robustMax !== null &&
13277
+ Number.isFinite(fits.robustMin) &&
13278
+ Number.isFinite(fits.robustMax) &&
13279
+ fits.robustMax > fits.robustMin) {
13280
+ return {
13281
+ min: fits.robustMin,
13282
+ max: fits.robustMax,
13283
+ };
13284
+ }
13285
+ return null;
13286
+ }
13287
+ getTilePhysicalRange(fits) {
13288
+ if (fits &&
13289
+ fits.physicalMin !== null &&
13290
+ fits.physicalMax !== null &&
13291
+ Number.isFinite(fits.physicalMin) &&
13292
+ Number.isFinite(fits.physicalMax) &&
13293
+ fits.physicalMax > fits.physicalMin) {
13294
+ return {
13295
+ min: fits.physicalMin,
13296
+ max: fits.physicalMax,
13297
+ };
13298
+ }
13299
+ return null;
13300
+ }
13301
+ getTileHeaderRange(fits) {
13302
+ if (fits &&
13303
+ fits.dataMin !== null &&
13304
+ fits.dataMax !== null &&
13305
+ Number.isFinite(fits.dataMin) &&
13306
+ Number.isFinite(fits.dataMax) &&
13307
+ fits.dataMax > fits.dataMin) {
13308
+ return {
13309
+ min: fits.dataMin,
13310
+ max: fits.dataMax,
13311
+ };
13312
+ }
13313
+ return null;
13314
+ }
12993
13315
  getCurrentHealpixOrder() {
12994
13316
  return this._visibleorder;
12995
13317
  }
@@ -13053,6 +13375,183 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
13053
13375
  exports.HiPS = HiPS;
13054
13376
 
13055
13377
 
13378
+ /***/ },
13379
+
13380
+ /***/ 9912
13381
+ (__unused_webpack_module, exports, __webpack_require__) {
13382
+
13383
+ var __webpack_unused_export__;
13384
+
13385
+ /*
13386
+ * AstroViewer
13387
+ * Copyright (C) Fabrizio Giordano
13388
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-AstroViewer-Commercial
13389
+ *
13390
+ * AstroViewer is dual-licensed under the GNU Affero General Public License
13391
+ * version 3 and a separate commercial license.
13392
+ *
13393
+ * See LICENSE.md, LICENSE-AGPL.md, and LICENSE-COMMERCIAL.md for details.
13394
+ */
13395
+ __webpack_unused_export__ = ({ value: true });
13396
+ exports.HiPSCoverage = void 0;
13397
+ const jsfitsio_1 = __webpack_require__(4971);
13398
+ class HiPSCoverage {
13399
+ intersec(intersec, arg1) {
13400
+ throw new Error("Method not implemented.");
13401
+ }
13402
+ ises(ises, expents, of, arg3) {
13403
+ throw new Error("Method not implemented.");
13404
+ }
13405
+ intersexpect(co, t, expe, arg3) {
13406
+ throw new Error("Method not implemented.");
13407
+ }
13408
+ intersectsT19(intersectsT19, arg1) {
13409
+ throw new Error("Method not implemented.");
13410
+ }
13411
+ _maxOrder = 0;
13412
+ _intervals = [];
13413
+ _loaded = false;
13414
+ get loaded() {
13415
+ return this._loaded;
13416
+ }
13417
+ get maxOrder() {
13418
+ return this._maxOrder;
13419
+ }
13420
+ get intervalCount() {
13421
+ return this._intervals.length;
13422
+ }
13423
+ clear() {
13424
+ this._maxOrder = 0;
13425
+ this._intervals = [];
13426
+ this._loaded = false;
13427
+ }
13428
+ async load(baseUrl) {
13429
+ const baseUrlString = baseUrl instanceof URL ? baseUrl.href : String(baseUrl);
13430
+ const normalizedBaseUrl = baseUrlString.endsWith("/")
13431
+ ? baseUrlString
13432
+ : `${baseUrlString}/`;
13433
+ await this.loadMoc(`${normalizedBaseUrl}Moc.fits`);
13434
+ }
13435
+ async loadMoc(mocUrl) {
13436
+ this.clear();
13437
+ const fits = await jsfitsio_1.FITSParser.loadFITSFile(mocUrl);
13438
+ if (!fits) {
13439
+ throw new Error("Unable to load HiPS Moc.fits");
13440
+ }
13441
+ const mocHDU = fits.hdus.find((hdu) => hdu instanceof jsfitsio_1.BinaryTableHDU);
13442
+ if (!(mocHDU instanceof jsfitsio_1.BinaryTableHDU)) {
13443
+ throw new Error("HiPS Moc.fits does not contain a BINTABLE HDU.");
13444
+ }
13445
+ const mocOrderItem = mocHDU.header.findById("MOCORDER");
13446
+ if (!mocOrderItem) {
13447
+ throw new Error("HiPS Moc.fits does not define MOCORDER.");
13448
+ }
13449
+ const maxOrder = Number(mocOrderItem.value);
13450
+ if (!Number.isInteger(maxOrder) || maxOrder < 0) {
13451
+ throw new Error(`Invalid MOCORDER value: ${mocOrderItem.value}`);
13452
+ }
13453
+ this._maxOrder = maxOrder;
13454
+ const intervals = [];
13455
+ for (let row = 0; row < mocHDU.rowCount; row++) {
13456
+ const rawUniq = mocHDU.getCell(row, "UNIQ");
13457
+ if (typeof rawUniq !== "number" ||
13458
+ !Number.isInteger(rawUniq) ||
13459
+ rawUniq < 4) {
13460
+ throw new Error(`Invalid MOC UNIQ value at row ${row}: ${String(rawUniq)}`);
13461
+ }
13462
+ const { order, pixel } = HiPSCoverage.decodeUniq(rawUniq);
13463
+ if (order > this._maxOrder) {
13464
+ throw new Error(`MOC cell order ${order} exceeds MOCORDER ${this._maxOrder}.`);
13465
+ }
13466
+ const scale = 4 ** (this._maxOrder - order);
13467
+ intervals.push({
13468
+ first: pixel * scale,
13469
+ last: (pixel + 1) * scale - 1,
13470
+ });
13471
+ }
13472
+ this._intervals = HiPSCoverage.mergeIntervals(intervals);
13473
+ this._loaded = true;
13474
+ }
13475
+ intersectsTile(order, pixel) {
13476
+ if (!this._loaded) {
13477
+ return true;
13478
+ }
13479
+ if (!Number.isInteger(order) ||
13480
+ order < 0 ||
13481
+ !Number.isInteger(pixel) ||
13482
+ pixel < 0) {
13483
+ return false;
13484
+ }
13485
+ let first;
13486
+ let last;
13487
+ if (order <= this._maxOrder) {
13488
+ const scale = 4 ** (this._maxOrder - order);
13489
+ first = pixel * scale;
13490
+ last = (pixel + 1) * scale - 1;
13491
+ }
13492
+ else {
13493
+ /*
13494
+ * At orders finer than the MOC resolution, reduce the tile
13495
+ * to its ancestor at MOCORDER.
13496
+ */
13497
+ const shift = 2 * (order - this._maxOrder);
13498
+ const ancestor = Math.floor(pixel / 2 ** shift);
13499
+ first = ancestor;
13500
+ last = ancestor;
13501
+ }
13502
+ return this.intersectsInterval(first, last);
13503
+ }
13504
+ intersectsInterval(first, last) {
13505
+ let low = 0;
13506
+ let high = this._intervals.length - 1;
13507
+ while (low <= high) {
13508
+ const middle = Math.floor((low + high) / 2);
13509
+ const interval = this._intervals[middle];
13510
+ if (interval.last < first) {
13511
+ low = middle + 1;
13512
+ continue;
13513
+ }
13514
+ if (interval.first > last) {
13515
+ high = middle - 1;
13516
+ continue;
13517
+ }
13518
+ return true;
13519
+ }
13520
+ return false;
13521
+ }
13522
+ static decodeUniq(uniq) {
13523
+ let order = 0;
13524
+ while (4 ** (order + 2) <= uniq) {
13525
+ order++;
13526
+ }
13527
+ const base = 4 ** (order + 1);
13528
+ return {
13529
+ order,
13530
+ pixel: uniq - base,
13531
+ };
13532
+ }
13533
+ static mergeIntervals(intervals) {
13534
+ if (intervals.length === 0) {
13535
+ return [];
13536
+ }
13537
+ intervals.sort((a, b) => a.first - b.first);
13538
+ const merged = [{ ...intervals[0] }];
13539
+ for (let i = 1; i < intervals.length; i++) {
13540
+ const current = intervals[i];
13541
+ const previous = merged[merged.length - 1];
13542
+ if (current.first <= previous.last + 1) {
13543
+ previous.last = Math.max(previous.last, current.last);
13544
+ }
13545
+ else {
13546
+ merged.push({ ...current });
13547
+ }
13548
+ }
13549
+ return merged;
13550
+ }
13551
+ }
13552
+ exports.HiPSCoverage = HiPSCoverage;
13553
+
13554
+
13056
13555
  /***/ },
13057
13556
 
13058
13557
  /***/ 5087
@@ -13254,7 +13753,7 @@ class Tile {
13254
13753
  vertexIndices = new Uint16Array();
13255
13754
  vertexIndexBuffer;
13256
13755
  _tileBuffer;
13257
- opacity = 1.0;
13756
+ // public opacity = 1.0;
13258
13757
  _webgl;
13259
13758
  _visibleTileManager;
13260
13759
  // private _hipsShaderProgram
@@ -13323,6 +13822,7 @@ class Tile {
13323
13822
  async loadFits() {
13324
13823
  try {
13325
13824
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
13825
+ this._hips.observeFITSDataRange(this._fitsTile);
13326
13826
  this._ready = true;
13327
13827
  }
13328
13828
  catch (error) {
@@ -13461,30 +13961,7 @@ class Tile {
13461
13961
  gl.bufferData(gl.ARRAY_BUFFER, this.vertexPosition[qidx], gl.STATIC_DRAW);
13462
13962
  }
13463
13963
  getFitsDataRange() {
13464
- const hipsRange = this._hips.dataRange;
13465
- if (hipsRange.min !== undefined &&
13466
- hipsRange.max !== undefined &&
13467
- Number.isFinite(hipsRange.min) &&
13468
- Number.isFinite(hipsRange.max) &&
13469
- hipsRange.max > hipsRange.min) {
13470
- return {
13471
- min: hipsRange.min,
13472
- max: hipsRange.max,
13473
- };
13474
- }
13475
- const fits = this._fitsTile;
13476
- if (fits &&
13477
- fits.dataMin !== null &&
13478
- fits.dataMax !== null &&
13479
- Number.isFinite(fits.dataMin) &&
13480
- Number.isFinite(fits.dataMax) &&
13481
- fits.dataMax > fits.dataMin) {
13482
- return {
13483
- min: fits.dataMin,
13484
- max: fits.dataMax,
13485
- };
13486
- }
13487
- return null;
13964
+ return this._hips.getFITSDisplayRange(this._fitsTile);
13488
13965
  }
13489
13966
  get inView() {
13490
13967
  return this._inView;
@@ -13588,7 +14065,7 @@ class Tile {
13588
14065
  console.error(`[Tile] No display range available for FITS tile ${this._texurl}`);
13589
14066
  return;
13590
14067
  }
13591
- hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
14068
+ hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
13592
14069
  }
13593
14070
  else {
13594
14071
  hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -13600,8 +14077,9 @@ class Tile {
13600
14077
  gl.bindTexture(gl.TEXTURE_2D, this._texture);
13601
14078
  const factorLocation = locations.textureAlpha[this._hipsShaderIndex];
13602
14079
  if (factorLocation) {
13603
- gl.uniform1f(factorLocation, this.opacity);
14080
+ gl.uniform1f(factorLocation, 1.0);
13604
14081
  }
14082
+ hipsShaderProgram.setLayerOpacity(this._hips.opacity);
13605
14083
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.vertexIndexBuffer);
13606
14084
  const elemno = this.vertexIndices.length;
13607
14085
  const indexType = this.vertexIndices instanceof Uint32Array
@@ -13625,6 +14103,9 @@ class Tile {
13625
14103
  const childTileNo = (this._tileno << 2) + c;
13626
14104
  const list = visibleTilesMap.get(childrenOrder);
13627
14105
  if (list.includes(childTileNo)) {
14106
+ if (!this._hips.intersectsCoverage(childrenOrder, childTileNo)) {
14107
+ continue;
14108
+ }
13628
14109
  const childTile = this._isGalacticHips
13629
14110
  ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
13630
14111
  : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
@@ -13715,6 +14196,9 @@ class TileBuffer {
13715
14196
  if (order > hips.maxOrder) {
13716
14197
  continue;
13717
14198
  }
14199
+ if (!hips.intersectsCoverage(order, tileno)) {
14200
+ continue;
14201
+ }
13718
14202
  this.getTile(tileno, order, hips);
13719
14203
  }
13720
14204
  }
@@ -13724,6 +14208,9 @@ class TileBuffer {
13724
14208
  if (order > hips.maxOrder) {
13725
14209
  continue;
13726
14210
  }
14211
+ if (!hips.intersectsCoverage(order, tileno)) {
14212
+ continue;
14213
+ }
13727
14214
  this.getGalTile(tileno, order, hips);
13728
14215
  }
13729
14216
  }
@@ -16423,6 +16910,8 @@ class HiPSShaderProgram {
16423
16910
  textureMode: "uTextureMode",
16424
16911
  dataMin: "uDataMin",
16425
16912
  dataMax: "uDataMax",
16913
+ scaleFunction: "uScaleFunction",
16914
+ scaleParam: "uScaleParam",
16426
16915
  };
16427
16916
  this.gl_attributes = {
16428
16917
  vertex_pos: "aVertexPosition",
@@ -16437,6 +16926,9 @@ class HiPSShaderProgram {
16437
16926
  textureMode: new Array(8).fill(null),
16438
16927
  dataMin: new Array(8).fill(null),
16439
16928
  dataMax: new Array(8).fill(null),
16929
+ scaleFunction: new Array(8).fill(null),
16930
+ scaleParam: new Array(8).fill(null),
16931
+ layerOpacity: null,
16440
16932
  clorMapIdx: null,
16441
16933
  vertexPositionAttribute: -1,
16442
16934
  textureCoordAttribute: -1,
@@ -16452,6 +16944,13 @@ class HiPSShaderProgram {
16452
16944
  gl.useProgram(this._shaderProgram);
16453
16945
  return this._shaderProgram;
16454
16946
  }
16947
+ setLayerOpacity(opacity) {
16948
+ const gl = this._webgl;
16949
+ this.locations.layerOpacity = gl.getUniformLocation(this.shaderProgram, "uLayerOpacity");
16950
+ if (this.locations.layerOpacity !== null) {
16951
+ gl.uniform1f(this.locations.layerOpacity, opacity);
16952
+ }
16953
+ }
16455
16954
  setRuntimeColorMap(colorMap) {
16456
16955
  this._runtimeColorMap = colorMap;
16457
16956
  }
@@ -16562,7 +17061,7 @@ class HiPSShaderProgram {
16562
17061
  }
16563
17062
  gl.useProgram(this.shaderProgram);
16564
17063
  }
16565
- setTextureDataMode(samplerIndex, fits, min, max) {
17064
+ setTextureDataMode(samplerIndex, fits, min, max, scaleFunction = 0, scaleParam = 1) {
16566
17065
  if (samplerIndex < 0 || samplerIndex >= 8) {
16567
17066
  throw new Error(`Invalid HiPS sampler index: ${samplerIndex}`);
16568
17067
  }
@@ -16583,12 +17082,20 @@ class HiPSShaderProgram {
16583
17082
  }
16584
17083
  const minLocation = this.locations.dataMin[samplerIndex];
16585
17084
  const maxLocation = this.locations.dataMax[samplerIndex];
17085
+ const scaleFunctionLocation = this.locations.scaleFunction[samplerIndex];
17086
+ const scaleParamLocation = this.locations.scaleParam[samplerIndex];
16586
17087
  if (minLocation) {
16587
17088
  gl.uniform1f(minLocation, min);
16588
17089
  }
16589
17090
  if (maxLocation) {
16590
17091
  gl.uniform1f(maxLocation, max);
16591
17092
  }
17093
+ if (scaleFunctionLocation) {
17094
+ gl.uniform1i(scaleFunctionLocation, scaleFunction);
17095
+ }
17096
+ if (scaleParamLocation) {
17097
+ gl.uniform1f(scaleParamLocation, scaleParam);
17098
+ }
16592
17099
  }
16593
17100
  enableShaders(pMatrix, vMatrix, mMatrix, colorMapIdx) {
16594
17101
  // const gl = global.gl
@@ -16604,6 +17111,8 @@ class HiPSShaderProgram {
16604
17111
  this.locations.textureMode[i] = gl.getUniformLocation(this.shaderProgram, `uTextureMode${i}`);
16605
17112
  this.locations.dataMin[i] = gl.getUniformLocation(this.shaderProgram, `uDataMin${i}`);
16606
17113
  this.locations.dataMax[i] = gl.getUniformLocation(this.shaderProgram, `uDataMax${i}`);
17114
+ this.locations.scaleFunction[i] = gl.getUniformLocation(this.shaderProgram, `uScaleFunction${i}`);
17115
+ this.locations.scaleParam[i] = gl.getUniformLocation(this.shaderProgram, `uScaleParam${i}`);
16607
17116
  if (this.locations.samplers[i]) {
16608
17117
  gl.uniform1i(this.locations.samplers[i], i);
16609
17118
  }
@@ -16940,7 +17449,7 @@ class ShaderManager {
16940
17449
  }
16941
17450
  static footprintVS() {
16942
17451
  return `#version 300 es
16943
- precision highp float;
17452
+ precision mediump float;
16944
17453
 
16945
17454
  layout(location = 0) in vec4 aCatPosition;
16946
17455
 
@@ -16982,10 +17491,11 @@ class ShaderManager {
16982
17491
  }
16983
17492
  static hipsNativeFS() {
16984
17493
  return `#version 300 es
16985
- precision mediump float;
17494
+ precision highp float;
16986
17495
 
16987
17496
  in vec2 vTextureCoord;
16988
17497
 
17498
+ uniform float uLayerOpacity;
16989
17499
  uniform float uFactor0;
16990
17500
  uniform float uFactor1;
16991
17501
  uniform float uFactor2;
@@ -17042,15 +17552,16 @@ class ShaderManager {
17042
17552
  sampleHiPSTexture7(vTextureCoord).rgb * uFactor7;
17043
17553
  }
17044
17554
 
17045
- fragColor = vec4(finalColor, 1.0);
17555
+ fragColor = vec4(finalColor, uLayerOpacity);
17046
17556
  }`;
17047
17557
  }
17048
17558
  static hipsGrayscaleFS() {
17049
17559
  return `#version 300 es
17050
- precision mediump float;
17560
+ precision highp float;
17051
17561
 
17052
17562
  in vec2 vTextureCoord;
17053
17563
 
17564
+ uniform float uLayerOpacity;
17054
17565
  uniform float uFactor0;
17055
17566
  uniform float uFactor1;
17056
17567
  uniform float uFactor2;
@@ -17115,12 +17626,14 @@ class ShaderManager {
17115
17626
  finalColor += color7.rgb * uFactor7;
17116
17627
  }
17117
17628
 
17118
- fragColor = vec4(finalColor, 1.0);
17629
+ fragColor = vec4(finalColor, uLayerOpacity);
17119
17630
  }`;
17120
17631
  }
17121
17632
  static hipsColorMapFS() {
17122
17633
  return `#version 300 es
17123
- precision mediump float;
17634
+ precision highp float;
17635
+
17636
+ uniform float uLayerOpacity;
17124
17637
 
17125
17638
  in vec2 vTextureCoord;
17126
17639
 
@@ -17214,7 +17727,7 @@ class ShaderManager {
17214
17727
  ) * uFactor7;
17215
17728
  }
17216
17729
 
17217
- fragColor = vec4(finalColor, 1.0);
17730
+ fragColor = vec4(finalColor, uLayerOpacity);
17218
17731
  }`;
17219
17732
  }
17220
17733
  static hipsSamplerGLSL(index) {
@@ -17223,6 +17736,36 @@ class ShaderManager {
17223
17736
  uniform int uTextureMode${index};
17224
17737
  uniform float uDataMin${index};
17225
17738
  uniform float uDataMax${index};
17739
+ uniform int uScaleFunction${index};
17740
+ uniform float uScaleParam${index};
17741
+
17742
+ float asinhCompat${index}(float value) {
17743
+ return log(value + sqrt(value * value + 1.0));
17744
+ }
17745
+
17746
+ float applyFitsScale${index}(float normalized) {
17747
+ float x = clamp(normalized, 0.0, 1.0);
17748
+
17749
+ if (uScaleFunction${index} == 1) {
17750
+ return sqrt(x);
17751
+ }
17752
+
17753
+ if (uScaleFunction${index} == 2) {
17754
+ float strength = max(uScaleParam${index}, 1.0);
17755
+ return log(1.0 + strength * x) / log(1.0 + strength);
17756
+ }
17757
+
17758
+ if (uScaleFunction${index} == 3) {
17759
+ float strength = max(uScaleParam${index}, 1.0);
17760
+ return asinhCompat${index}(strength * x) / asinhCompat${index}(strength);
17761
+ }
17762
+
17763
+ if (uScaleFunction${index} == 4) {
17764
+ return pow(x, max(uScaleParam${index}, 0.0001));
17765
+ }
17766
+
17767
+ return x;
17768
+ }
17226
17769
 
17227
17770
  vec4 sampleHiPSTexture${index}(vec2 uv) {
17228
17771
  vec4 color = texture(uSampler${index}, uv);
@@ -17246,10 +17789,12 @@ class ShaderManager {
17246
17789
  1.0
17247
17790
  );
17248
17791
 
17792
+ float scaled = applyFitsScale${index}(normalized);
17793
+
17249
17794
  return vec4(
17250
- normalized,
17251
- normalized,
17252
- normalized,
17795
+ scaled,
17796
+ scaled,
17797
+ scaled,
17253
17798
  1.0
17254
17799
  );
17255
17800
  }
@@ -19783,11 +20328,12 @@ function findCircleThroughTwoPoints(points, q1, q2) {
19783
20328
 
19784
20329
  // EXPORTS
19785
20330
  __webpack_require__.d(__webpack_exports__, {
20331
+ BinaryTableHDU: () => (/* reexport */ BinaryTableHDU),
19786
20332
  FITSHeaderManager: () => (/* reexport */ FITSHeaderManager),
19787
20333
  FITSParser: () => (/* reexport */ FITSParser)
19788
20334
  });
19789
20335
 
19790
- // UNUSED EXPORTS: AsciiTableHDU, BinaryTableHDU, FITSFile, FITSHDU, FITSHeaderItem, FITSWriter, ImageHDU, ParseHeader, ParsePayload, PrimaryHDU
20336
+ // UNUSED EXPORTS: AsciiTableHDU, FITSFile, FITSHDU, FITSHeaderItem, FITSWriter, ImageHDU, ParseHeader, ParsePayload, PrimaryHDU
19791
20337
 
19792
20338
  ;// ./node_modules/jsfitsio/lib-esm/model/FITSHeaderItem.js
19793
20339
  /**