astro-viewer 3.1.0 → 3.2.1

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 (50) hide show
  1. package/dist/astroviewer.cjs +713 -204
  2. package/dist/astroviewer.js +1 -1
  3. package/dist/astroviewer.min.js +1 -1
  4. package/lib-esm/AstroSphere.d.ts +19 -15
  5. package/lib-esm/AstroSphere.js +170 -94
  6. package/lib-esm/AstroViewer.d.ts +2 -0
  7. package/lib-esm/AstroViewer.js +3 -0
  8. package/lib-esm/Camera.js +4 -6
  9. package/lib-esm/Config.d.ts +2 -0
  10. package/lib-esm/Config.js +3 -1
  11. package/lib-esm/index.d.ts +2 -0
  12. package/lib-esm/index.js +1 -0
  13. package/lib-esm/model/AbstractSkyEntity.d.ts +1 -0
  14. package/lib-esm/model/MetadataManager.d.ts +1 -0
  15. package/lib-esm/model/MetadataManager.js +4 -1
  16. package/lib-esm/model/Point.d.ts +9 -1
  17. package/lib-esm/model/Point.js +18 -0
  18. package/lib-esm/model/SphereFoV.js +4 -3
  19. package/lib-esm/model/earth/XYZConfig.d.ts +4 -0
  20. package/lib-esm/model/earth/XYZFoVHelper.d.ts +5 -2
  21. package/lib-esm/model/earth/XYZFoVHelper.js +40 -3
  22. package/lib-esm/model/footprints/Footprint.d.ts +4 -1
  23. package/lib-esm/model/footprints/Footprint.js +18 -2
  24. package/lib-esm/model/footprints/FootprintSetGL.d.ts +4 -2
  25. package/lib-esm/model/footprints/FootprintSetGL.js +9 -6
  26. package/lib-esm/model/grid/EquatorialGrid.d.ts +2 -1
  27. package/lib-esm/model/grid/EquatorialGrid.js +10 -6
  28. package/lib-esm/model/grid/HealpixGrid.js +5 -2
  29. package/lib-esm/model/grid/LonLatGrid.d.ts +3 -0
  30. package/lib-esm/model/grid/LonLatGrid.js +61 -10
  31. package/lib-esm/model/hips/FoVHelper.d.ts +5 -2
  32. package/lib-esm/model/hips/FoVHelper.js +40 -3
  33. package/lib-esm/model/hips/HiPS.d.ts +2 -0
  34. package/lib-esm/model/hips/HiPS.js +26 -5
  35. package/lib-esm/model/hips/HiPSConfig.d.ts +13 -0
  36. package/lib-esm/model/hips/HiPSConfig.js +1 -0
  37. package/lib-esm/model/hips/Tile.d.ts +1 -0
  38. package/lib-esm/model/hips/Tile.js +3 -0
  39. package/lib-esm/model/hips/TileBuffer.d.ts +5 -0
  40. package/lib-esm/model/hips/TileBuffer.js +49 -0
  41. package/lib-esm/model/terra/TerraFootprintSetGL.d.ts +6 -0
  42. package/lib-esm/model/terra/TerraFootprintSetGL.js +42 -0
  43. package/lib-esm/utils/CoordsType.d.ts +2 -1
  44. package/lib-esm/utils/CoordsType.js +1 -0
  45. package/lib-esm/utils/GeoJSONParser.d.ts +19 -0
  46. package/lib-esm/utils/GeoJSONParser.js +112 -0
  47. package/lib-esm/utils/PerspectiveMatrixManager.js +4 -1
  48. package/lib-esm/utils/STCSParser.d.ts +7 -3
  49. package/lib-esm/utils/STCSParser.js +15 -9
  50. package/package.json +3 -2
@@ -1,17 +1,18 @@
1
- import Camera from './Camera.js';
2
- import { AstroCoords, HMS, SphericalCoords, DMS } from './utils/Utils.js';
3
- import { HiPS } from './model/hips/HiPS.js';
4
- import { HiPSDescriptor } from './model/hips/HiPSDescriptor.js';
5
- import { SphereFoV } from './model/SphereFoV.js';
6
- import { Point } from './model/Point.js';
7
- import { CatalogueGL } from './model/catalogues/CatalogueGL.js';
8
- import { FootprintSetGL, HoveredFootprintDetail } from './model/footprints/FootprintSetGL.js';
9
- import { EquatorialGrid } from './model/grid/EquatorialGrid.js';
10
- import { HealpixGrid } from './model/grid/HealpixGrid.js';
11
- import { ColorMap } from './model/ColorMaps.js';
12
- import type { WMTSLayerConfig, XYZDebugStats, XYZLayerConfig } from './model/earth/XYZConfig.js';
13
- import { XYZMapDescriptor } from './model/earth/XYZMapDescriptor.js';
14
- import { XYZMap } from './model/earth/XYZMap.js';
1
+ import Camera from "./Camera.js";
2
+ import { AstroCoords, HMS, SphericalCoords, DMS } from "./utils/Utils.js";
3
+ import { HiPS } from "./model/hips/HiPS.js";
4
+ import { HiPSDescriptor } from "./model/hips/HiPSDescriptor.js";
5
+ import { SphereFoV } from "./model/SphereFoV.js";
6
+ import { Point } from "./model/Point.js";
7
+ import { CatalogueGL } from "./model/catalogues/CatalogueGL.js";
8
+ import { FootprintSetGL, HoveredFootprintDetail } from "./model/footprints/FootprintSetGL.js";
9
+ import { EquatorialGrid } from "./model/grid/EquatorialGrid.js";
10
+ import { HealpixGrid } from "./model/grid/HealpixGrid.js";
11
+ import { ColorMap } from "./model/ColorMaps.js";
12
+ import type { WMTSLayerConfig, XYZDebugStats, XYZLayerConfig } from "./model/earth/XYZConfig.js";
13
+ import type { HiPSDebugStats } from "./model/hips/HiPSConfig.js";
14
+ import { XYZMapDescriptor } from "./model/earth/XYZMapDescriptor.js";
15
+ import { XYZMap } from "./model/earth/XYZMap.js";
15
16
  export type PointCoordinates = {
16
17
  astroDeg: AstroCoords;
17
18
  raHMS: HMS;
@@ -66,6 +67,8 @@ declare class AstroSphere {
66
67
  private _webgl;
67
68
  private _selectedColorMap;
68
69
  private _cameraStatusChanged;
70
+ private lastCameraChangedAt;
71
+ private lastCameraMotionAt;
69
72
  private lastHoveredSource;
70
73
  private lastHoveredCatalogue;
71
74
  private zoomSensitivity;
@@ -107,7 +110,7 @@ declare class AstroSphere {
107
110
  deleteFootprintSet(footprintSet: FootprintSetGL): void;
108
111
  getHoveredFootprints(): HoveredFootprintDetail[];
109
112
  goTo(raDeg: number, decDeg: number): void;
110
- getActiveCoordinateMode(): 'equatorial' | 'galactic' | 'lonlat';
113
+ getActiveCoordinateMode(): "equatorial" | "galactic" | "lonlat";
111
114
  resetAxesOrientation(): void;
112
115
  setKeepCameraNorthUp(enabled: boolean): void;
113
116
  isKeepCameraNorthUp(): boolean;
@@ -136,6 +139,7 @@ declare class AstroSphere {
136
139
  setNorthSouthRotationLocked(locked: boolean): void;
137
140
  isNorthSouthRotationLocked(): boolean;
138
141
  getXYZDebugStats(): XYZDebugStats;
142
+ getHiPSDebugStats(): HiPSDebugStats | null;
139
143
  draw(canvas: HTMLCanvasElement): void;
140
144
  private emitHoveredSourceIfChanged;
141
145
  }
@@ -10,25 +10,25 @@
10
10
  *
11
11
  * See LICENSE.md, LICENSE-COMMERCIAL.md, and LICENSE-NONCOMMERCIAL.md for details.
12
12
  */
13
- import { bootSetup } from './Config.js';
14
- import Camera from './Camera.js';
15
- import RayPickingUtils from './utils/RayPickingUtils.js';
16
- import global from './Global.js';
17
- import MouseHelper from './utils/MouseHelper.js';
18
- import { cartesianToSpherical, sphericalToAstroDeg, raDegToHMS, decDegToDMS, } from './utils/Utils.js';
19
- import { HiPS } from './model/hips/HiPS.js';
20
- import { PerspectiveMatrixManager } from './utils/PerspectiveMatrixManager.js';
21
- import { Point } from './model/Point.js';
22
- import { FoVUtils } from './utils/FoVUtils.js';
23
- import { EquatorialGrid } from './model/grid/EquatorialGrid.js';
24
- import { HealpixGrid } from './model/grid/HealpixGrid.js';
25
- import { CoordsType } from './utils/CoordsType.js';
26
- import ColorMaps from './model/ColorMaps.js';
27
- import { xyzTileRequestScheduler } from './model/earth/XYZTileRequestScheduler.js';
28
- import { WMTSAdapter } from './model/earth/WMTSAdapter.js';
29
- import { XYZMapDescriptor } from './model/earth/XYZMapDescriptor.js';
30
- import { XYZMap } from './model/earth/XYZMap.js';
31
- import { mat4, vec3, vec4 } from 'gl-matrix';
13
+ import { bootSetup } from "./Config.js";
14
+ import Camera from "./Camera.js";
15
+ import RayPickingUtils from "./utils/RayPickingUtils.js";
16
+ import global from "./Global.js";
17
+ import MouseHelper from "./utils/MouseHelper.js";
18
+ import { cartesianToSpherical, sphericalToAstroDeg, raDegToHMS, decDegToDMS, } from "./utils/Utils.js";
19
+ import { HiPS } from "./model/hips/HiPS.js";
20
+ import { PerspectiveMatrixManager } from "./utils/PerspectiveMatrixManager.js";
21
+ import { Point } from "./model/Point.js";
22
+ import { FoVUtils } from "./utils/FoVUtils.js";
23
+ import { EquatorialGrid } from "./model/grid/EquatorialGrid.js";
24
+ import { HealpixGrid } from "./model/grid/HealpixGrid.js";
25
+ import { CoordsType } from "./utils/CoordsType.js";
26
+ import ColorMaps from "./model/ColorMaps.js";
27
+ import { xyzTileRequestScheduler } from "./model/earth/XYZTileRequestScheduler.js";
28
+ import { WMTSAdapter } from "./model/earth/WMTSAdapter.js";
29
+ import { XYZMapDescriptor } from "./model/earth/XYZMapDescriptor.js";
30
+ import { XYZMap } from "./model/earth/XYZMap.js";
31
+ import { mat4, vec3, vec4 } from "gl-matrix";
32
32
  /**
33
33
  * AstroSphere — main WebGL scene controller (TS port)
34
34
  */
@@ -62,19 +62,21 @@ class AstroSphere {
62
62
  _webgl;
63
63
  _selectedColorMap;
64
64
  _cameraStatusChanged = false;
65
+ lastCameraChangedAt = 0;
66
+ lastCameraMotionAt = 0;
65
67
  lastHoveredSource = null;
66
68
  lastHoveredCatalogue = null;
67
69
  zoomSensitivity = 1.0;
68
70
  lockedEastWestRaDeg = null;
69
71
  lockedNorthSouthDecDeg = null;
70
- keepCameraNorthUp = false;
72
+ keepCameraNorthUp = true;
71
73
  constructor(canvas, webgl) {
72
- console.log('[AstroSphere] new instance for canvas', canvas.id);
74
+ console.log("[AstroSphere] new instance for canvas", canvas.id);
73
75
  // Keep global GL context (as in original JS)
74
76
  this._webgl = webgl;
75
77
  this.mouseHelper = new MouseHelper();
76
78
  this.canvas = canvas;
77
- const nativeColorMap = 'native';
79
+ const nativeColorMap = "native";
78
80
  this._selectedColorMap = ColorMaps[nativeColorMap];
79
81
  global.insideSphere = bootSetup.insideSphere;
80
82
  this.initCamera();
@@ -122,20 +124,26 @@ class AstroSphere {
122
124
  astroDeg: astroCoords,
123
125
  sphericalDeg: sphericalCoords,
124
126
  raHMS: raHMS,
125
- decDMS: decDMS
127
+ decDMS: decDMS,
126
128
  };
127
129
  return this.centralPoinCoords;
128
130
  }
129
131
  updateLastMousePoint() {
130
- const sphericalCoords = { phi: this.mouseHelper.phi, theta: this.mouseHelper.theta };
131
- const astroCoords = { ra: this.mouseHelper.ra, dec: this.mouseHelper.dec };
132
+ const sphericalCoords = {
133
+ phi: this.mouseHelper.phi,
134
+ theta: this.mouseHelper.theta,
135
+ };
136
+ const astroCoords = {
137
+ ra: this.mouseHelper.ra,
138
+ dec: this.mouseHelper.dec,
139
+ };
132
140
  const raHMS = this.mouseHelper.raHMS;
133
141
  const decDMS = this.mouseHelper.decDMS;
134
142
  this.mousePointCoords = {
135
143
  astroDeg: astroCoords,
136
144
  sphericalDeg: sphericalCoords,
137
145
  raHMS: raHMS,
138
- decDMS: decDMS
146
+ decDMS: decDMS,
139
147
  };
140
148
  return this.mousePointCoords;
141
149
  }
@@ -155,9 +163,6 @@ class AstroSphere {
155
163
  computeZoomStep(currentFov, deltaY) {
156
164
  const direction = deltaY < 0 ? -1 : 1;
157
165
  const wheelScale = this.clamp(Math.abs(deltaY) / 120, AstroSphere.MIN_WHEEL_SCALE, AstroSphere.MAX_WHEEL_SCALE);
158
- // Continuous wheel response:
159
- // - broad FoV stays responsive without large jumps
160
- // - narrow FoV keeps a usable floor to avoid the 0.1 -> 0.02 deg stall
161
166
  const baseMagnitude = this.clamp(0.0012 + 0.0025 * Math.sqrt(Math.max(currentFov, 0)), 0.0012, 0.04);
162
167
  return direction * baseMagnitude * wheelScale * this.zoomSensitivity;
163
168
  }
@@ -247,7 +252,8 @@ class AstroSphere {
247
252
  };
248
253
  }
249
254
  enforceAstronomicalRotationLocks() {
250
- if (this.lockedEastWestRaDeg == null && this.lockedNorthSouthDecDeg == null) {
255
+ if (this.lockedEastWestRaDeg == null &&
256
+ this.lockedNorthSouthDecDeg == null) {
251
257
  return false;
252
258
  }
253
259
  const center = this.updateCentralPoint();
@@ -290,7 +296,7 @@ class AstroSphere {
290
296
  return;
291
297
  // optional debug
292
298
  // console.log('[AstroSphere] emit camera-changed:', reason);
293
- this.canvas.dispatchEvent(new CustomEvent('camera-changed', {
299
+ this.canvas.dispatchEvent(new CustomEvent("camera-changed", {
294
300
  detail,
295
301
  bubbles: true,
296
302
  composed: true,
@@ -298,7 +304,7 @@ class AstroSphere {
298
304
  }
299
305
  addEventListeners(canvas) {
300
306
  if (global.debug) {
301
- console.log('[AstroSphere::addEventListeners]');
307
+ console.log("[AstroSphere::addEventListeners]");
302
308
  }
303
309
  const CLICK_MAX_DISTANCE_PX = 4;
304
310
  const CLICK_MAX_DURATION_MS = 250;
@@ -328,7 +334,7 @@ class AstroSphere {
328
334
  const handleMouseUp = (event) => {
329
335
  canvas.releasePointerCapture(event.pointerId);
330
336
  this.mouseDown = false;
331
- document.body.style.cursor = 'auto';
337
+ document.body.style.cursor = "auto";
332
338
  if (event.button !== 0) {
333
339
  event.preventDefault();
334
340
  return false;
@@ -350,7 +356,7 @@ class AstroSphere {
350
356
  const clickResult = cat.selectPrimarySourceFromClick(this.mouseHelper);
351
357
  if (!clickResult?.sources.length)
352
358
  continue;
353
- this._webgl.canvas.dispatchEvent(new CustomEvent('source-clicked', {
359
+ this._webgl.canvas.dispatchEvent(new CustomEvent("source-clicked", {
354
360
  detail: {
355
361
  source: clickResult.sources,
356
362
  selectionState: clickResult.selectionState,
@@ -364,7 +370,7 @@ class AstroSphere {
364
370
  const clickResult = fset.selectPrimaryFootprintFromClick(this.mouseHelper);
365
371
  if (!clickResult?.footprints.length)
366
372
  continue;
367
- this._webgl.canvas.dispatchEvent(new CustomEvent('footprint-clicked', {
373
+ this._webgl.canvas.dispatchEvent(new CustomEvent("footprint-clicked", {
368
374
  detail: {
369
375
  footprint: clickResult.footprints,
370
376
  selectionState: clickResult.selectionState,
@@ -391,10 +397,13 @@ class AstroSphere {
391
397
  if (!this._healpixGrid)
392
398
  return;
393
399
  if (this.mouseDown) {
394
- document.body.style.cursor = 'grab';
395
- // Rotation deltas either use client-space or local-space, but be consistent
396
- const deltaX = ((newX - (this.lastMouseX ?? newX)) * Math.PI) / canvas.width;
397
- const deltaY = ((newY - (this.lastMouseY ?? newY)) * Math.PI) / canvas.height;
400
+ document.body.style.cursor = "grab";
401
+ const dragDirection = global.insideSphere ? -1 : 1;
402
+ const dragSpeed = global.insideSphere ? 10.0 : 1;
403
+ const deltaX = (dragDirection * dragSpeed * (newX - (this.lastMouseX ?? newX)) * Math.PI) /
404
+ canvas.width;
405
+ const deltaY = (dragDirection * dragSpeed * (newY - (this.lastMouseY ?? newY)) * Math.PI) /
406
+ canvas.height;
398
407
  const filteredDelta = this.filterRotationDeltaByAstroLocks(deltaX, deltaY);
399
408
  this.inertiaX += 0.1 * filteredDelta.deltaX;
400
409
  this.inertiaY += 0.1 * filteredDelta.deltaY;
@@ -429,10 +438,11 @@ class AstroSphere {
429
438
  // direction feels responsive instead of "buffered".
430
439
  this.zoomInertia = 0;
431
440
  this._camera.zoom(zoomStep);
441
+ this.lastCameraMotionAt = performance.now();
432
442
  this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
433
443
  this._camera.refreshFoV(this.fov.minFoV);
434
444
  this._cameraStatusChanged = true;
435
- this.emitCameraChanged('wheel');
445
+ this.emitCameraChanged("wheel");
436
446
  event.preventDefault();
437
447
  };
438
448
  const handleContextMenu = (event) => {
@@ -450,7 +460,7 @@ class AstroSphere {
450
460
  const pickResult = cat.getSourcesFromPointer(this.mouseHelper);
451
461
  if (!pickResult?.sources.length)
452
462
  continue;
453
- this._webgl.canvas.dispatchEvent(new CustomEvent('source-contextmenu', {
463
+ this._webgl.canvas.dispatchEvent(new CustomEvent("source-contextmenu", {
454
464
  detail: {
455
465
  source: pickResult.sources,
456
466
  catalogue: cat,
@@ -466,7 +476,7 @@ class AstroSphere {
466
476
  const pickResult = fset.getFootprintsFromPointer(this.mouseHelper);
467
477
  if (!pickResult?.footprints.length)
468
478
  continue;
469
- this._webgl.canvas.dispatchEvent(new CustomEvent('footprint-contextmenu', {
479
+ this._webgl.canvas.dispatchEvent(new CustomEvent("footprint-contextmenu", {
470
480
  detail: {
471
481
  footprint: pickResult.footprints,
472
482
  footprintSet: fset,
@@ -486,40 +496,40 @@ class AstroSphere {
486
496
  }
487
497
  // console.log('[AstroSphere::onKeyDown] key=', evt.key)
488
498
  switch (evt.key) {
489
- case '1':
499
+ case "1":
490
500
  // Free camera
491
501
  this._camera.clearRotationLock();
492
502
  break;
493
- case '2':
503
+ case "2":
494
504
  // Lock X axis rotation
495
505
  this._camera.setRotationLock({ x: true, y: false, z: false });
496
506
  break;
497
- case '3':
507
+ case "3":
498
508
  // Lock Y axis rotation
499
509
  this._camera.setRotationLock({ x: false, y: true, z: false });
500
510
  break;
501
- case '4':
511
+ case "4":
502
512
  // Lock Z axis rotation
503
513
  this._camera.setRotationLock({ x: false, y: false, z: true });
504
514
  break;
505
515
  }
506
516
  };
507
- console.log('[AstroSphere] registering pointer and wheel listeners on canvas');
517
+ console.log("[AstroSphere] registering pointer and wheel listeners on canvas");
508
518
  canvas.onpointerdown = handleMouseDown;
509
519
  canvas.onpointerup = handleMouseUp;
510
520
  canvas.onpointermove = handleMouseMove;
511
521
  canvas.onpointerleave = () => {
512
522
  this.clearLastMousePoint();
513
523
  this._cameraStatusChanged = true;
514
- this.emitCameraChanged('pointerleave');
524
+ this.emitCameraChanged("pointerleave");
515
525
  };
516
- console.log('[AstroSphere] adding wheel event listener with passive: false');
517
- canvas.addEventListener('wheel', handleMouseWheel, { passive: false });
518
- canvas.addEventListener('contextmenu', handleContextMenu);
519
- console.log('[AstroSphere] registering global keydown listener on document');
520
- document.addEventListener('keydown', onKeyDown, { capture: true });
526
+ console.log("[AstroSphere] adding wheel event listener with passive: false");
527
+ canvas.addEventListener("wheel", handleMouseWheel, { passive: false });
528
+ canvas.addEventListener("contextmenu", handleContextMenu);
529
+ console.log("[AstroSphere] registering global keydown listener on document");
530
+ document.addEventListener("keydown", onKeyDown, { capture: true });
521
531
  }
522
- // REVIEW THIS METHOD AND MOVE IT
532
+ // REVIEW THIS METHOD AND MOVE IT
523
533
  getPhiThetaDeg(canvas) {
524
534
  const rect = canvas.getBoundingClientRect();
525
535
  const maxX = rect.width;
@@ -546,21 +556,21 @@ class AstroSphere {
546
556
  }
547
557
  activateHiPS(hipsDescriptor) {
548
558
  this._activeHiPS = new HiPS(1, [0.0, 0.0, 0.0], 0, 0, hipsDescriptor, this._webgl, this._healpixGrid);
549
- this._activeBaseLayer = 'hips';
559
+ this._activeBaseLayer = "hips";
550
560
  }
551
561
  activateXYZ(config) {
552
- this.activateXYZ2(new XYZMapDescriptor(config.name ?? 'XYZ Earth2 Layer', config.urlTemplate, config.minZoom ?? 0, config.maxZoom ?? 8, config.segmentsPerSide ?? 48, config.maxCachedTiles ?? 384, 8, config.urlResolver));
553
- this._activeBaseLayer = 'xyz';
562
+ this.activateXYZ2(new XYZMapDescriptor(config.name ?? "XYZ Earth2 Layer", config.urlTemplate, config.minZoom ?? 0, config.maxZoom ?? 8, config.segmentsPerSide ?? 48, config.maxCachedTiles ?? 384, 8, config.urlResolver));
563
+ this._activeBaseLayer = "xyz";
554
564
  }
555
565
  activateXYZ2(config) {
556
566
  this._activeXYZ2 = new XYZMap(1, [0.0, 0.0, 0.0], 0, 0, config, this._webgl);
557
- this._activeBaseLayer = 'xyz';
567
+ this._activeBaseLayer = "xyz";
558
568
  }
559
569
  activateWMTS(config) {
560
570
  const adapter = new WMTSAdapter(config);
561
571
  const xyzConfig = adapter.toXYZLayerConfig();
562
- this._activeXYZ2 = new XYZMap(1, [0.0, 0.0, 0.0], 0, 0, new XYZMapDescriptor(config.layer ? `WMTS ${config.layer}` : 'WMTS Earth2 Layer', xyzConfig.urlTemplate, xyzConfig.minZoom ?? 0, xyzConfig.maxZoom ?? 8, xyzConfig.segmentsPerSide ?? 48, xyzConfig.maxCachedTiles ?? 384, 8, xyzConfig.urlResolver), this._webgl);
563
- this._activeBaseLayer = 'xyz';
572
+ this._activeXYZ2 = new XYZMap(1, [0.0, 0.0, 0.0], 0, 0, new XYZMapDescriptor(config.layer ? `WMTS ${config.layer}` : "WMTS Earth2 Layer", xyzConfig.urlTemplate, xyzConfig.minZoom ?? 0, xyzConfig.maxZoom ?? 8, xyzConfig.segmentsPerSide ?? 48, xyzConfig.maxCachedTiles ?? 384, 8, xyzConfig.urlResolver), this._webgl);
573
+ this._activeBaseLayer = "xyz";
564
574
  }
565
575
  // Catalogue section
566
576
  async showCatalogue(cat) {
@@ -570,7 +580,7 @@ class AstroSphere {
570
580
  return cat;
571
581
  }
572
582
  deleteCatalogue(catalogue) {
573
- this.activeCatalogues = this.activeCatalogues.filter(c => c !== catalogue);
583
+ this.activeCatalogues = this.activeCatalogues.filter((c) => c !== catalogue);
574
584
  }
575
585
  // End Catalogue section
576
586
  // Footprint section
@@ -581,11 +591,11 @@ class AstroSphere {
581
591
  return fset;
582
592
  }
583
593
  deleteFootprintSet(footprintSet) {
584
- this.activeFootprintSets = this.activeFootprintSets.filter(fst => fst !== footprintSet);
594
+ this.activeFootprintSets = this.activeFootprintSets.filter((fst) => fst !== footprintSet);
585
595
  }
586
596
  getHoveredFootprints() {
587
597
  let footprintsHovered = [];
588
- this.activeFootprintSets.forEach(fset => {
598
+ this.activeFootprintSets.forEach((fset) => {
589
599
  footprintsHovered.push(fset.hoveredFootprints);
590
600
  });
591
601
  return footprintsHovered;
@@ -595,13 +605,13 @@ class AstroSphere {
595
605
  this._camera.goTo(raDeg, decDeg);
596
606
  }
597
607
  getActiveCoordinateMode() {
598
- if (this._activeBaseLayer === 'xyz') {
599
- return 'lonlat';
608
+ if (this._activeBaseLayer === "xyz") {
609
+ return "lonlat";
600
610
  }
601
- if (this._activeBaseLayer === 'hips' && this._activeHiPS?.isGalacticHips) {
602
- return 'galactic';
611
+ if (this._activeBaseLayer === "hips" && this._activeHiPS?.isGalacticHips) {
612
+ return "galactic";
603
613
  }
604
- return 'equatorial';
614
+ return "equatorial";
605
615
  }
606
616
  resetAxesOrientation() {
607
617
  const center = this.updateCentralPoint();
@@ -624,7 +634,7 @@ class AstroSphere {
624
634
  return this.keepCameraNorthUp;
625
635
  }
626
636
  getFoV() {
627
- if (this._activeBaseLayer === 'xyz' && this._activeXYZ2) {
637
+ if (this._activeBaseLayer === "xyz" && this._activeXYZ2) {
628
638
  return this._activeXYZ2.getFoV();
629
639
  }
630
640
  return this.fov;
@@ -641,11 +651,15 @@ class AstroSphere {
641
651
  this._camera.refreshFoV(this.fov.minFoV);
642
652
  }
643
653
  changeFoV2(deg) {
644
- const newCameraPos = this._healpixGrid.getFoV().computeCameraPositionForFoV(deg);
654
+ const newCameraPos = this._healpixGrid
655
+ .getFoV()
656
+ .computeCameraPositionForFoV(deg);
645
657
  this._camera.setCameraPosition(newCameraPos);
646
658
  }
647
659
  changeFoV3(deg) {
648
- const newPos = this._healpixGrid.getFoV().computeCameraPositionForAngularDiameter(deg);
660
+ const newPos = this._healpixGrid
661
+ .getFoV()
662
+ .computeCameraPositionForAngularDiameter(deg);
649
663
  this._camera.setCameraPosition(newPos);
650
664
  // Recompute projection after moving the camera
651
665
  this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, bootSetup.camera_fov_deg, bootSetup.camera_near_plane, false);
@@ -654,9 +668,22 @@ class AstroSphere {
654
668
  return global.insideSphere;
655
669
  }
656
670
  toggleInsideSphere() {
671
+ const centerBeforeToggle = this.updateCentralPoint();
672
+ this.inertiaX = 0;
673
+ this.inertiaY = 0;
674
+ this.zoomInertia = 0;
657
675
  global.insideSphere = !global.insideSphere;
658
676
  // console.log(global.insideSphere)
659
677
  this._camera.toggleInsideSphere();
678
+ this._camera.goTo(centerBeforeToggle.astroDeg.ra, centerBeforeToggle.astroDeg.dec);
679
+ this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, bootSetup.camera_fov_deg, bootSetup.camera_near_plane, global.insideSphere);
680
+ this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
681
+ this._camera.refreshFoV(this.fov.minFoV);
682
+ this.updateCentralPoint();
683
+ this.lastCameraMotionAt = performance.now();
684
+ this._cameraStatusChanged = true;
685
+ this.emitCameraChanged("inside-sphere-toggle");
686
+ requestAnimationFrame(() => this.draw(this.canvas));
660
687
  }
661
688
  // imposta posizione camera
662
689
  setCameraPosition(pos) {
@@ -691,6 +718,13 @@ class AstroSphere {
691
718
  if (centralradeg == null || centraldecdeg == null) {
692
719
  return null;
693
720
  }
721
+ let fovPolygon = [];
722
+ try {
723
+ fovPolygon = this.getFoVPolygon();
724
+ }
725
+ catch (error) {
726
+ console.warn("[AstroSphere] getCurrentStatus: FoV polygon is not available.", error);
727
+ }
694
728
  // if (this._rotating && centraldecdeg && centralradeg) {
695
729
  const detail = {
696
730
  fovDeg: this.fov.minFoV,
@@ -705,7 +739,7 @@ class AstroSphere {
705
739
  centralPoint: new Point({ raDeg: centralradeg, decDeg: centraldecdeg }, CoordsType.ASTRO),
706
740
  mouseHoverPoint: this.mousePointCoords,
707
741
  colorMap: this._selectedColorMap,
708
- getFoVPolygon: this.getFoVPolygon(),
742
+ getFoVPolygon: fovPolygon,
709
743
  };
710
744
  return detail;
711
745
  // }
@@ -737,7 +771,9 @@ class AstroSphere {
737
771
  this._camera.setRotationLock({ y: locked });
738
772
  if (locked)
739
773
  this.inertiaX = 0;
740
- this.lockedEastWestRaDeg = locked ? this.updateCentralPoint()?.astroDeg.ra ?? null : null;
774
+ this.lockedEastWestRaDeg = locked
775
+ ? (this.updateCentralPoint()?.astroDeg.ra ?? null)
776
+ : null;
741
777
  }
742
778
  isEastWestRotationLocked() {
743
779
  return this._camera.isRotationLockedY();
@@ -746,7 +782,9 @@ class AstroSphere {
746
782
  this._camera.setRotationLock({ x: locked });
747
783
  if (locked)
748
784
  this.inertiaY = 0;
749
- this.lockedNorthSouthDecDeg = locked ? this.updateCentralPoint()?.astroDeg.dec ?? null : null;
785
+ this.lockedNorthSouthDecDeg = locked
786
+ ? (this.updateCentralPoint()?.astroDeg.dec ?? null)
787
+ : null;
750
788
  }
751
789
  isNorthSouthRotationLocked() {
752
790
  return this._camera.isRotationLockedX();
@@ -758,6 +796,11 @@ class AstroSphere {
758
796
  requests: xyzTileRequestScheduler.getDebugStats(),
759
797
  };
760
798
  }
799
+ getHiPSDebugStats() {
800
+ if (!this._activeHiPS)
801
+ return null;
802
+ return this._activeHiPS.getDebugStats();
803
+ }
761
804
  draw(canvas) {
762
805
  if (this._refreshingStatus)
763
806
  return;
@@ -783,6 +826,7 @@ class AstroSphere {
783
826
  if (Math.abs(this.zoomInertia) > 0.0001) {
784
827
  this._camera.zoom(this.zoomInertia);
785
828
  this.zoomInertia *= 0.95;
829
+ this.lastCameraMotionAt = performance.now();
786
830
  this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
787
831
  this._camera.refreshFoV(this.fov.minFoV);
788
832
  if (this.prevFov !== this.fov.minFoV) {
@@ -798,7 +842,9 @@ class AstroSphere {
798
842
  this._cameraStatusChanged = true;
799
843
  }
800
844
  // Rotation inertia
801
- if (this.mouseDown || Math.abs(this.inertiaX) > 0.02 || Math.abs(this.inertiaY) > 0.02) {
845
+ if (this.mouseDown ||
846
+ Math.abs(this.inertiaX) > 0.02 ||
847
+ Math.abs(this.inertiaY) > 0.02) {
802
848
  cameraRotated = true;
803
849
  const filteredInertia = this.filterRotationDeltaByAstroLocks(this.inertiaX, this.inertiaY);
804
850
  PHI = filteredInertia.deltaX;
@@ -806,6 +852,7 @@ class AstroSphere {
806
852
  this.inertiaX = filteredInertia.deltaX * 0.95;
807
853
  this.inertiaY = filteredInertia.deltaY * 0.95;
808
854
  this._camera.rotate(PHI, THETA);
855
+ this.lastCameraMotionAt = performance.now();
809
856
  this._perspectiveMatrixManager.computePerspectiveMatrix(canvas, this._camera, bootSetup.camera_fov_deg, bootSetup.camera_near_plane, global.insideSphere);
810
857
  const lockCorrected = this.enforceAstronomicalRotationLocks();
811
858
  if (!lockCorrected) {
@@ -816,6 +863,12 @@ class AstroSphere {
816
863
  this.inertiaY = 0;
817
864
  this.inertiaX = 0;
818
865
  }
866
+ const nextFoV = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
867
+ if (Number.isFinite(nextFoV.minFoV) && nextFoV.minFoV > 0) {
868
+ this.fov = nextFoV;
869
+ this._camera.refreshFoV(this.fov.minFoV);
870
+ this.prevFov = this.fov.minFoV;
871
+ }
819
872
  // Se la camera è ruotata (anche solo per inerzia), aggiorna punto centrale + emetti cameraChanged
820
873
  if (cameraRotated) {
821
874
  // Ricalcola il punto centrale
@@ -833,16 +886,20 @@ class AstroSphere {
833
886
  }
834
887
  }
835
888
  if (this._cameraStatusChanged) {
836
- const detail = this.getCurrentStatus();
889
+ const now = performance.now();
890
+ const shouldEmitCameraChanged = !this.mouseDown || now - this.lastCameraChangedAt > 100;
891
+ const detail = shouldEmitCameraChanged ? this.getCurrentStatus() : null;
837
892
  if (detail) {
838
893
  // console.log('[AstroSphere::draw] emitting camera-changed event due to camera status change', detail)
839
894
  // console.log('[AstroSphere::draw] inertia', this.zoomInertia, this.inertiaX, this.inertiaY)
840
- this.canvas.dispatchEvent(new CustomEvent('camera-changed', {
895
+ this.canvas.dispatchEvent(new CustomEvent("camera-changed", {
841
896
  detail,
842
- bubbles: true, composed: true,
897
+ bubbles: true,
898
+ composed: true,
843
899
  }));
900
+ this.lastCameraChangedAt = now;
844
901
  }
845
- if (!this.startup) {
902
+ if (!this.startup && shouldEmitCameraChanged) {
846
903
  this._cameraStatusChanged = false;
847
904
  }
848
905
  }
@@ -852,23 +909,39 @@ class AstroSphere {
852
909
  this._webgl.enable(this._webgl.CULL_FACE);
853
910
  this._webgl.cullFace(global.insideSphere ? this._webgl.FRONT : this._webgl.BACK);
854
911
  this._webgl.blendFunc(this._webgl.SRC_ALPHA, this._webgl.ONE_MINUS_SRC_ALPHA);
855
- if (this._activeBaseLayer === 'hips' && this._activeHiPS) {
912
+ if (this._activeBaseLayer === "hips" && this._activeHiPS) {
856
913
  const visibleOrder = Math.min(this._healpixGrid.visibleorder, this._activeHiPS.maxOrder);
857
914
  this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
858
915
  }
859
916
  // DRAW HiPS
917
+ const stableFovDeg = this.fov?.minFoV ?? this._healpixGrid.getMinFoV();
918
+ const nowForGrid = performance.now();
919
+ const cameraMovingForGrid = this.mouseDown ||
920
+ Math.abs(this.zoomInertia) > 0.0001 ||
921
+ Math.abs(this.inertiaX) > 0.02 ||
922
+ Math.abs(this.inertiaY) > 0.02 ||
923
+ nowForGrid - this.lastCameraMotionAt < 220;
924
+ // const skyEntityDrawInput: SkyEntityDrawInput = {
925
+ // fovDeg: this._healpixGrid.getMinFoV(),
926
+ // camera: this._camera,
927
+ // pMatrix: this._perspectiveMatrixManager.pMatrix,
928
+ // centerSphericalDeg: this.updateCentralPoint().sphericalDeg,
929
+ // fovPolygon: this._activeBaseLayer === 'xyz' ? this.getFoVPolygon() : undefined,
930
+ // viewportSphericalSamples: this._activeBaseLayer === 'xyz' ? this.collectViewportSphericalSamples(7) : undefined,
931
+ // }
860
932
  const skyEntityDrawInput = {
861
- fovDeg: this._healpixGrid.getMinFoV(),
933
+ fovDeg: stableFovDeg,
862
934
  camera: this._camera,
863
935
  pMatrix: this._perspectiveMatrixManager.pMatrix,
864
936
  centerSphericalDeg: this.updateCentralPoint().sphericalDeg,
865
- fovPolygon: this._activeBaseLayer === 'xyz' ? this.getFoVPolygon() : undefined,
866
- viewportSphericalSamples: this._activeBaseLayer === 'xyz' ? this.collectViewportSphericalSamples(7) : undefined,
937
+ fovPolygon: undefined,
938
+ viewportSphericalSamples: undefined,
939
+ cameraMoving: cameraMovingForGrid,
867
940
  };
868
- if (this._activeBaseLayer === 'hips') {
941
+ if (this._activeBaseLayer === "hips") {
869
942
  this._activeHiPS?.draw(skyEntityDrawInput);
870
943
  }
871
- if (this._activeBaseLayer === 'xyz') {
944
+ if (this._activeBaseLayer === "xyz") {
872
945
  this._activeXYZ2?.draw(skyEntityDrawInput);
873
946
  }
874
947
  this._healpixGrid.draw(skyEntityDrawInput);
@@ -881,24 +954,27 @@ class AstroSphere {
881
954
  const raDecDeg = sphericalToAstroDeg(phiTheta.phi, phiTheta.theta);
882
955
  const raHMS = raDegToHMS(raDecDeg.ra);
883
956
  const decDMS = decDegToDMS(raDecDeg.dec);
884
- this.prevFov = this._healpixGrid.getMinFoV();
957
+ // this.prevFov = this._healpixGrid.getMinFoV();
958
+ this.prevFov = this.fov?.minFoV ?? this._healpixGrid.getMinFoV();
885
959
  this._cameraStatusChanged = true;
886
- console.log('(startup coords)', {
960
+ console.log("(startup coords)", {
887
961
  raDeg: raDecDeg.ra,
888
962
  decDeg: raDecDeg.dec,
889
963
  raHMS,
890
964
  decDMS,
891
965
  });
892
966
  }
893
- this.activeCatalogues.forEach(cat => {
894
- const activeModelMatrix = this._activeHiPS?.getModelMatrix() ?? this._activeXYZ2?.getModelMatrix();
967
+ this.activeCatalogues.forEach((cat) => {
968
+ const activeModelMatrix = this._activeHiPS?.getModelMatrix() ??
969
+ this._activeXYZ2?.getModelMatrix();
895
970
  if (activeModelMatrix) {
896
971
  cat.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
897
972
  }
898
973
  });
899
974
  this.emitHoveredSourceIfChanged();
900
- this.activeFootprintSets.forEach(fst => {
901
- const activeModelMatrix = this._activeHiPS?.getModelMatrix() ?? this._activeXYZ2?.getModelMatrix();
975
+ this.activeFootprintSets.forEach((fst) => {
976
+ const activeModelMatrix = this._activeHiPS?.getModelMatrix() ??
977
+ this._activeXYZ2?.getModelMatrix();
902
978
  if (activeModelMatrix) {
903
979
  fst.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
904
980
  }
@@ -915,13 +991,13 @@ class AstroSphere {
915
991
  nextHoveredCatalogue = cat;
916
992
  break;
917
993
  }
918
- const unchanged = nextHoveredSource === this.lastHoveredSource
919
- && nextHoveredCatalogue === this.lastHoveredCatalogue;
994
+ const unchanged = nextHoveredSource === this.lastHoveredSource &&
995
+ nextHoveredCatalogue === this.lastHoveredCatalogue;
920
996
  if (unchanged)
921
997
  return;
922
998
  this.lastHoveredSource = nextHoveredSource;
923
999
  this.lastHoveredCatalogue = nextHoveredCatalogue;
924
- this._webgl.canvas.dispatchEvent(new CustomEvent('source-hovered', {
1000
+ this._webgl.canvas.dispatchEvent(new CustomEvent("source-hovered", {
925
1001
  detail: { source: nextHoveredSource, catalogue: nextHoveredCatalogue },
926
1002
  bubbles: true,
927
1003
  composed: true,