astro-viewer 3.1.0 → 3.2.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 (50) hide show
  1. package/dist/astroviewer.cjs +706 -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 +163 -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 +2 -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) {
@@ -705,7 +732,7 @@ class AstroSphere {
705
732
  centralPoint: new Point({ raDeg: centralradeg, decDeg: centraldecdeg }, CoordsType.ASTRO),
706
733
  mouseHoverPoint: this.mousePointCoords,
707
734
  colorMap: this._selectedColorMap,
708
- getFoVPolygon: this.getFoVPolygon(),
735
+ getFoVPolygon: [],
709
736
  };
710
737
  return detail;
711
738
  // }
@@ -737,7 +764,9 @@ class AstroSphere {
737
764
  this._camera.setRotationLock({ y: locked });
738
765
  if (locked)
739
766
  this.inertiaX = 0;
740
- this.lockedEastWestRaDeg = locked ? this.updateCentralPoint()?.astroDeg.ra ?? null : null;
767
+ this.lockedEastWestRaDeg = locked
768
+ ? (this.updateCentralPoint()?.astroDeg.ra ?? null)
769
+ : null;
741
770
  }
742
771
  isEastWestRotationLocked() {
743
772
  return this._camera.isRotationLockedY();
@@ -746,7 +775,9 @@ class AstroSphere {
746
775
  this._camera.setRotationLock({ x: locked });
747
776
  if (locked)
748
777
  this.inertiaY = 0;
749
- this.lockedNorthSouthDecDeg = locked ? this.updateCentralPoint()?.astroDeg.dec ?? null : null;
778
+ this.lockedNorthSouthDecDeg = locked
779
+ ? (this.updateCentralPoint()?.astroDeg.dec ?? null)
780
+ : null;
750
781
  }
751
782
  isNorthSouthRotationLocked() {
752
783
  return this._camera.isRotationLockedX();
@@ -758,6 +789,11 @@ class AstroSphere {
758
789
  requests: xyzTileRequestScheduler.getDebugStats(),
759
790
  };
760
791
  }
792
+ getHiPSDebugStats() {
793
+ if (!this._activeHiPS)
794
+ return null;
795
+ return this._activeHiPS.getDebugStats();
796
+ }
761
797
  draw(canvas) {
762
798
  if (this._refreshingStatus)
763
799
  return;
@@ -783,6 +819,7 @@ class AstroSphere {
783
819
  if (Math.abs(this.zoomInertia) > 0.0001) {
784
820
  this._camera.zoom(this.zoomInertia);
785
821
  this.zoomInertia *= 0.95;
822
+ this.lastCameraMotionAt = performance.now();
786
823
  this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
787
824
  this._camera.refreshFoV(this.fov.minFoV);
788
825
  if (this.prevFov !== this.fov.minFoV) {
@@ -798,7 +835,9 @@ class AstroSphere {
798
835
  this._cameraStatusChanged = true;
799
836
  }
800
837
  // Rotation inertia
801
- if (this.mouseDown || Math.abs(this.inertiaX) > 0.02 || Math.abs(this.inertiaY) > 0.02) {
838
+ if (this.mouseDown ||
839
+ Math.abs(this.inertiaX) > 0.02 ||
840
+ Math.abs(this.inertiaY) > 0.02) {
802
841
  cameraRotated = true;
803
842
  const filteredInertia = this.filterRotationDeltaByAstroLocks(this.inertiaX, this.inertiaY);
804
843
  PHI = filteredInertia.deltaX;
@@ -806,6 +845,7 @@ class AstroSphere {
806
845
  this.inertiaX = filteredInertia.deltaX * 0.95;
807
846
  this.inertiaY = filteredInertia.deltaY * 0.95;
808
847
  this._camera.rotate(PHI, THETA);
848
+ this.lastCameraMotionAt = performance.now();
809
849
  this._perspectiveMatrixManager.computePerspectiveMatrix(canvas, this._camera, bootSetup.camera_fov_deg, bootSetup.camera_near_plane, global.insideSphere);
810
850
  const lockCorrected = this.enforceAstronomicalRotationLocks();
811
851
  if (!lockCorrected) {
@@ -816,6 +856,12 @@ class AstroSphere {
816
856
  this.inertiaY = 0;
817
857
  this.inertiaX = 0;
818
858
  }
859
+ const nextFoV = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
860
+ if (Number.isFinite(nextFoV.minFoV) && nextFoV.minFoV > 0) {
861
+ this.fov = nextFoV;
862
+ this._camera.refreshFoV(this.fov.minFoV);
863
+ this.prevFov = this.fov.minFoV;
864
+ }
819
865
  // Se la camera è ruotata (anche solo per inerzia), aggiorna punto centrale + emetti cameraChanged
820
866
  if (cameraRotated) {
821
867
  // Ricalcola il punto centrale
@@ -833,16 +879,20 @@ class AstroSphere {
833
879
  }
834
880
  }
835
881
  if (this._cameraStatusChanged) {
836
- const detail = this.getCurrentStatus();
882
+ const now = performance.now();
883
+ const shouldEmitCameraChanged = !this.mouseDown || now - this.lastCameraChangedAt > 100;
884
+ const detail = shouldEmitCameraChanged ? this.getCurrentStatus() : null;
837
885
  if (detail) {
838
886
  // console.log('[AstroSphere::draw] emitting camera-changed event due to camera status change', detail)
839
887
  // console.log('[AstroSphere::draw] inertia', this.zoomInertia, this.inertiaX, this.inertiaY)
840
- this.canvas.dispatchEvent(new CustomEvent('camera-changed', {
888
+ this.canvas.dispatchEvent(new CustomEvent("camera-changed", {
841
889
  detail,
842
- bubbles: true, composed: true,
890
+ bubbles: true,
891
+ composed: true,
843
892
  }));
893
+ this.lastCameraChangedAt = now;
844
894
  }
845
- if (!this.startup) {
895
+ if (!this.startup && shouldEmitCameraChanged) {
846
896
  this._cameraStatusChanged = false;
847
897
  }
848
898
  }
@@ -852,23 +902,39 @@ class AstroSphere {
852
902
  this._webgl.enable(this._webgl.CULL_FACE);
853
903
  this._webgl.cullFace(global.insideSphere ? this._webgl.FRONT : this._webgl.BACK);
854
904
  this._webgl.blendFunc(this._webgl.SRC_ALPHA, this._webgl.ONE_MINUS_SRC_ALPHA);
855
- if (this._activeBaseLayer === 'hips' && this._activeHiPS) {
905
+ if (this._activeBaseLayer === "hips" && this._activeHiPS) {
856
906
  const visibleOrder = Math.min(this._healpixGrid.visibleorder, this._activeHiPS.maxOrder);
857
907
  this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
858
908
  }
859
909
  // DRAW HiPS
910
+ const stableFovDeg = this.fov?.minFoV ?? this._healpixGrid.getMinFoV();
911
+ const nowForGrid = performance.now();
912
+ const cameraMovingForGrid = this.mouseDown ||
913
+ Math.abs(this.zoomInertia) > 0.0001 ||
914
+ Math.abs(this.inertiaX) > 0.02 ||
915
+ Math.abs(this.inertiaY) > 0.02 ||
916
+ nowForGrid - this.lastCameraMotionAt < 220;
917
+ // const skyEntityDrawInput: SkyEntityDrawInput = {
918
+ // fovDeg: this._healpixGrid.getMinFoV(),
919
+ // camera: this._camera,
920
+ // pMatrix: this._perspectiveMatrixManager.pMatrix,
921
+ // centerSphericalDeg: this.updateCentralPoint().sphericalDeg,
922
+ // fovPolygon: this._activeBaseLayer === 'xyz' ? this.getFoVPolygon() : undefined,
923
+ // viewportSphericalSamples: this._activeBaseLayer === 'xyz' ? this.collectViewportSphericalSamples(7) : undefined,
924
+ // }
860
925
  const skyEntityDrawInput = {
861
- fovDeg: this._healpixGrid.getMinFoV(),
926
+ fovDeg: stableFovDeg,
862
927
  camera: this._camera,
863
928
  pMatrix: this._perspectiveMatrixManager.pMatrix,
864
929
  centerSphericalDeg: this.updateCentralPoint().sphericalDeg,
865
- fovPolygon: this._activeBaseLayer === 'xyz' ? this.getFoVPolygon() : undefined,
866
- viewportSphericalSamples: this._activeBaseLayer === 'xyz' ? this.collectViewportSphericalSamples(7) : undefined,
930
+ fovPolygon: undefined,
931
+ viewportSphericalSamples: undefined,
932
+ cameraMoving: cameraMovingForGrid,
867
933
  };
868
- if (this._activeBaseLayer === 'hips') {
934
+ if (this._activeBaseLayer === "hips") {
869
935
  this._activeHiPS?.draw(skyEntityDrawInput);
870
936
  }
871
- if (this._activeBaseLayer === 'xyz') {
937
+ if (this._activeBaseLayer === "xyz") {
872
938
  this._activeXYZ2?.draw(skyEntityDrawInput);
873
939
  }
874
940
  this._healpixGrid.draw(skyEntityDrawInput);
@@ -881,24 +947,27 @@ class AstroSphere {
881
947
  const raDecDeg = sphericalToAstroDeg(phiTheta.phi, phiTheta.theta);
882
948
  const raHMS = raDegToHMS(raDecDeg.ra);
883
949
  const decDMS = decDegToDMS(raDecDeg.dec);
884
- this.prevFov = this._healpixGrid.getMinFoV();
950
+ // this.prevFov = this._healpixGrid.getMinFoV();
951
+ this.prevFov = this.fov?.minFoV ?? this._healpixGrid.getMinFoV();
885
952
  this._cameraStatusChanged = true;
886
- console.log('(startup coords)', {
953
+ console.log("(startup coords)", {
887
954
  raDeg: raDecDeg.ra,
888
955
  decDeg: raDecDeg.dec,
889
956
  raHMS,
890
957
  decDMS,
891
958
  });
892
959
  }
893
- this.activeCatalogues.forEach(cat => {
894
- const activeModelMatrix = this._activeHiPS?.getModelMatrix() ?? this._activeXYZ2?.getModelMatrix();
960
+ this.activeCatalogues.forEach((cat) => {
961
+ const activeModelMatrix = this._activeHiPS?.getModelMatrix() ??
962
+ this._activeXYZ2?.getModelMatrix();
895
963
  if (activeModelMatrix) {
896
964
  cat.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
897
965
  }
898
966
  });
899
967
  this.emitHoveredSourceIfChanged();
900
- this.activeFootprintSets.forEach(fst => {
901
- const activeModelMatrix = this._activeHiPS?.getModelMatrix() ?? this._activeXYZ2?.getModelMatrix();
968
+ this.activeFootprintSets.forEach((fst) => {
969
+ const activeModelMatrix = this._activeHiPS?.getModelMatrix() ??
970
+ this._activeXYZ2?.getModelMatrix();
902
971
  if (activeModelMatrix) {
903
972
  fst.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
904
973
  }
@@ -915,13 +984,13 @@ class AstroSphere {
915
984
  nextHoveredCatalogue = cat;
916
985
  break;
917
986
  }
918
- const unchanged = nextHoveredSource === this.lastHoveredSource
919
- && nextHoveredCatalogue === this.lastHoveredCatalogue;
987
+ const unchanged = nextHoveredSource === this.lastHoveredSource &&
988
+ nextHoveredCatalogue === this.lastHoveredCatalogue;
920
989
  if (unchanged)
921
990
  return;
922
991
  this.lastHoveredSource = nextHoveredSource;
923
992
  this.lastHoveredCatalogue = nextHoveredCatalogue;
924
- this._webgl.canvas.dispatchEvent(new CustomEvent('source-hovered', {
993
+ this._webgl.canvas.dispatchEvent(new CustomEvent("source-hovered", {
925
994
  detail: { source: nextHoveredSource, catalogue: nextHoveredCatalogue },
926
995
  bubbles: true,
927
996
  composed: true,