astro-viewer 3.2.1 → 3.3.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.
@@ -13,6 +13,9 @@ import type { WMTSLayerConfig, XYZDebugStats, XYZLayerConfig } from "./model/ear
13
13
  import type { HiPSDebugStats } from "./model/hips/HiPSConfig.js";
14
14
  import { XYZMapDescriptor } from "./model/earth/XYZMapDescriptor.js";
15
15
  import { XYZMap } from "./model/earth/XYZMap.js";
16
+ import { MeshHiPS } from "./model/meships/MeshHiPS.js";
17
+ import { MeshHiPSDescriptor } from "./model/meships/MeshHiPSDescriptor.js";
18
+ import type { MeshHiPSDebugStats } from "./model/meships/MeshHiPSTypes.js";
16
19
  export type PointCoordinates = {
17
20
  astroDeg: AstroCoords;
18
21
  raHMS: HMS;
@@ -59,6 +62,7 @@ declare class AstroSphere {
59
62
  private pointerDownAt;
60
63
  private _activeHiPS;
61
64
  private _activeXYZ2;
65
+ private _activeMeshHiPS;
62
66
  private _activeBaseLayer;
63
67
  private startup;
64
68
  private fov;
@@ -103,6 +107,7 @@ declare class AstroSphere {
103
107
  activateHiPS(hipsDescriptor: HiPSDescriptor): void;
104
108
  activateXYZ(config: XYZLayerConfig): void;
105
109
  activateXYZ2(config: XYZMapDescriptor): void;
110
+ activateMeshHiPS(descriptor: MeshHiPSDescriptor): void;
106
111
  activateWMTS(config: WMTSLayerConfig): void;
107
112
  showCatalogue(cat: CatalogueGL): Promise<CatalogueGL>;
108
113
  deleteCatalogue(catalogue: CatalogueGL): void;
@@ -132,6 +137,7 @@ declare class AstroSphere {
132
137
  private prevCentralDecDeg;
133
138
  get activeHiPS(): HiPS | null;
134
139
  get activeXYZ(): XYZMap | null;
140
+ get activeMeshHiPS(): MeshHiPS | null;
135
141
  isLonLatGridVisible(): boolean;
136
142
  toggleLonLatGrid(): boolean;
137
143
  setEastWestRotationLocked(locked: boolean): void;
@@ -140,6 +146,7 @@ declare class AstroSphere {
140
146
  isNorthSouthRotationLocked(): boolean;
141
147
  getXYZDebugStats(): XYZDebugStats;
142
148
  getHiPSDebugStats(): HiPSDebugStats | null;
149
+ getMeshHiPSDebugStats(): MeshHiPSDebugStats | null;
143
150
  draw(canvas: HTMLCanvasElement): void;
144
151
  private emitHoveredSourceIfChanged;
145
152
  }
@@ -28,6 +28,7 @@ import { xyzTileRequestScheduler } from "./model/earth/XYZTileRequestScheduler.j
28
28
  import { WMTSAdapter } from "./model/earth/WMTSAdapter.js";
29
29
  import { XYZMapDescriptor } from "./model/earth/XYZMapDescriptor.js";
30
30
  import { XYZMap } from "./model/earth/XYZMap.js";
31
+ import { MeshHiPS } from "./model/meships/MeshHiPS.js";
31
32
  import { mat4, vec3, vec4 } from "gl-matrix";
32
33
  /**
33
34
  * AstroSphere — main WebGL scene controller (TS port)
@@ -54,6 +55,7 @@ class AstroSphere {
54
55
  pointerDownAt = 0;
55
56
  _activeHiPS = null;
56
57
  _activeXYZ2 = null;
58
+ _activeMeshHiPS = null;
57
59
  _activeBaseLayer = null;
58
60
  startup = true;
59
61
  fov;
@@ -287,7 +289,7 @@ class AstroSphere {
287
289
  }
288
290
  emitCameraChanged(reason) {
289
291
  // avoid dispatch before scene is ready
290
- if (!this._activeHiPS)
292
+ if (!this._activeHiPS && !this._activeXYZ2 && !this._activeMeshHiPS)
291
293
  return;
292
294
  if (!this._healpixGrid?.fovObj)
293
295
  return;
@@ -566,6 +568,10 @@ class AstroSphere {
566
568
  this._activeXYZ2 = new XYZMap(1, [0.0, 0.0, 0.0], 0, 0, config, this._webgl);
567
569
  this._activeBaseLayer = "xyz";
568
570
  }
571
+ activateMeshHiPS(descriptor) {
572
+ this._activeMeshHiPS = new MeshHiPS(descriptor.meshRadius, [0.0, 0.0, 0.0], 0, 0, descriptor, this._webgl, this._healpixGrid);
573
+ this._activeBaseLayer = "meships";
574
+ }
569
575
  activateWMTS(config) {
570
576
  const adapter = new WMTSAdapter(config);
571
577
  const xyzConfig = adapter.toXYZLayerConfig();
@@ -746,7 +752,7 @@ class AstroSphere {
746
752
  // return null
747
753
  }
748
754
  changeColorMap(cm) {
749
- if (!this._activeHiPS && !this._activeXYZ2)
755
+ if (!this._activeHiPS && !this._activeXYZ2 && !this._activeMeshHiPS)
750
756
  return;
751
757
  this._selectedColorMap = cm;
752
758
  this._activeHiPS?.changeColorMap(cm);
@@ -761,6 +767,9 @@ class AstroSphere {
761
767
  get activeXYZ() {
762
768
  return this._activeXYZ2;
763
769
  }
770
+ get activeMeshHiPS() {
771
+ return this._activeMeshHiPS;
772
+ }
764
773
  isLonLatGridVisible() {
765
774
  return this._activeXYZ2?.isLonLatGridVisible() ?? false;
766
775
  }
@@ -801,12 +810,17 @@ class AstroSphere {
801
810
  return null;
802
811
  return this._activeHiPS.getDebugStats();
803
812
  }
813
+ getMeshHiPSDebugStats() {
814
+ if (!this._activeMeshHiPS)
815
+ return null;
816
+ return this._activeMeshHiPS.getDebugStats();
817
+ }
804
818
  draw(canvas) {
805
819
  if (this._refreshingStatus)
806
820
  return;
807
821
  if (!this._webgl)
808
822
  return;
809
- if (!this._activeHiPS && !this._activeXYZ2)
823
+ if (!this._activeHiPS && !this._activeXYZ2 && !this._activeMeshHiPS)
810
824
  return;
811
825
  if (!this._healpixGrid || Object.keys(this._healpixGrid).length === 0)
812
826
  return;
@@ -913,6 +927,10 @@ class AstroSphere {
913
927
  const visibleOrder = Math.min(this._healpixGrid.visibleorder, this._activeHiPS.maxOrder);
914
928
  this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
915
929
  }
930
+ if (this._activeBaseLayer === "meships" && this._activeMeshHiPS) {
931
+ const visibleOrder = this._activeMeshHiPS.refreshOrder(this.fov?.minFoV ?? this._healpixGrid.getMinFoV());
932
+ this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
933
+ }
916
934
  // DRAW HiPS
917
935
  const stableFovDeg = this.fov?.minFoV ?? this._healpixGrid.getMinFoV();
918
936
  const nowForGrid = performance.now();
@@ -944,6 +962,9 @@ class AstroSphere {
944
962
  if (this._activeBaseLayer === "xyz") {
945
963
  this._activeXYZ2?.draw(skyEntityDrawInput);
946
964
  }
965
+ if (this._activeBaseLayer === "meships") {
966
+ this._activeMeshHiPS?.draw(skyEntityDrawInput);
967
+ }
947
968
  this._healpixGrid.draw(skyEntityDrawInput);
948
969
  this._equatorialGrid.draw(skyEntityDrawInput);
949
970
  this._webgl.enable(this._webgl.DEPTH_TEST);
@@ -966,7 +987,8 @@ class AstroSphere {
966
987
  }
967
988
  this.activeCatalogues.forEach((cat) => {
968
989
  const activeModelMatrix = this._activeHiPS?.getModelMatrix() ??
969
- this._activeXYZ2?.getModelMatrix();
990
+ this._activeXYZ2?.getModelMatrix() ??
991
+ this._activeMeshHiPS?.getModelMatrix();
970
992
  if (activeModelMatrix) {
971
993
  cat.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
972
994
  }
@@ -974,7 +996,8 @@ class AstroSphere {
974
996
  this.emitHoveredSourceIfChanged();
975
997
  this.activeFootprintSets.forEach((fst) => {
976
998
  const activeModelMatrix = this._activeHiPS?.getModelMatrix() ??
977
- this._activeXYZ2?.getModelMatrix();
999
+ this._activeXYZ2?.getModelMatrix() ??
1000
+ this._activeMeshHiPS?.getModelMatrix();
978
1001
  if (activeModelMatrix) {
979
1002
  fst.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
980
1003
  }
@@ -14,6 +14,7 @@ import type { HiPSDebugStats } from './model/hips/HiPSConfig.js';
14
14
  import { XYZMap } from './model/earth/XYZMap.js';
15
15
  import { TerraPointSetGL } from './model/terra/TerraPointSetGL.js';
16
16
  import { TerraFootprintSetGL } from './model/terra/TerraFootprintSetGL.js';
17
+ import type { MeshHiPSConfig, MeshHiPSDebugStats } from './model/meships/MeshHiPSTypes.js';
17
18
  export declare class AstroViewer {
18
19
  private astroSphere;
19
20
  private canvas;
@@ -54,11 +55,14 @@ export declare class AstroViewer {
54
55
  name?: string;
55
56
  }): void;
56
57
  activateWMTS(config: WMTSLayerConfig): void;
58
+ activateMeshHiPS(config: MeshHiPSConfig): void;
57
59
  setXYZMaxConcurrentRequests(value: number): void;
58
60
  getXYZMaxConcurrentRequests(): number;
59
61
  getXYZDebugStats(): XYZDebugStats;
60
62
  getHiPSDebugStats(): HiPSDebugStats | null;
63
+ getMeshHiPSDebugStats(): MeshHiPSDebugStats | null;
61
64
  loadHiPS(baseUrl: string): Promise<string>;
65
+ loadMeshHiPS(baseUrl: string, config?: Omit<MeshHiPSConfig, 'baseUrl'>): Promise<string>;
62
66
  changeColorMap(colorMapName: ColorMapName): void;
63
67
  changeCustomColorMap(colorMap: ColorMap): void;
64
68
  getActiveHiPS(): HiPS | null;
@@ -21,6 +21,7 @@ import { xyzTileRequestScheduler } from './model/earth/XYZTileRequestScheduler.j
21
21
  import { XYZMapDescriptor } from './model/earth/XYZMapDescriptor.js';
22
22
  import { TerraPointSetGL } from './model/terra/TerraPointSetGL.js';
23
23
  import { TerraFootprintSetGL } from './model/terra/TerraFootprintSetGL.js';
24
+ import { MeshHiPSDescriptor } from './model/meships/MeshHiPSDescriptor.js';
24
25
  // & {
25
26
  // viewportWidth: number
26
27
  // viewportHeight: number
@@ -136,6 +137,9 @@ export class AstroViewer {
136
137
  activateWMTS(config) {
137
138
  this.astroSphere.activateWMTS(config);
138
139
  }
140
+ activateMeshHiPS(config) {
141
+ this.astroSphere.activateMeshHiPS(new MeshHiPSDescriptor(config));
142
+ }
139
143
  setXYZMaxConcurrentRequests(value) {
140
144
  xyzTileRequestScheduler.setMaxConcurrent(value);
141
145
  }
@@ -148,6 +152,9 @@ export class AstroViewer {
148
152
  getHiPSDebugStats() {
149
153
  return this.astroSphere.getHiPSDebugStats();
150
154
  }
155
+ getMeshHiPSDebugStats() {
156
+ return this.astroSphere.getMeshHiPSDebugStats();
157
+ }
151
158
  async loadHiPS(baseUrl) {
152
159
  const hipsUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
153
160
  const resp = await fetch(hipsUrl + 'properties');
@@ -159,6 +166,26 @@ export class AstroViewer {
159
166
  return desc.surveyName;
160
167
  // this.activateHiPS(desc);
161
168
  }
169
+ async loadMeshHiPS(baseUrl, config = {}) {
170
+ const meshHiPSUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
171
+ let propsText = '';
172
+ const resp = await fetch(meshHiPSUrl + 'properties');
173
+ if (resp.ok) {
174
+ propsText = await resp.text();
175
+ }
176
+ else {
177
+ const jsonResp = await fetch(meshHiPSUrl + 'properties.json');
178
+ if (!jsonResp.ok)
179
+ throw new Error(`HTTP ${jsonResp.status} fetching MeshHiPS properties`);
180
+ const json = await jsonResp.json();
181
+ propsText = Object.entries(json)
182
+ .map(([key, value]) => `${key}=${String(value)}`)
183
+ .join('\n');
184
+ }
185
+ const desc = new MeshHiPSDescriptor({ ...config, baseUrl: meshHiPSUrl }, propsText);
186
+ this.astroSphere.activateMeshHiPS(desc);
187
+ return desc.name;
188
+ }
162
189
  // changeColorMap(hips: HiPS, colorMapName: ColorMapName) {
163
190
  changeColorMap(colorMapName) {
164
191
  const colorMap = ColorMaps[colorMapName];
@@ -19,7 +19,10 @@ export type { ColorMap, ColorMapName } from './model/ColorMaps.js';
19
19
  export { HiPS } from './model/hips/HiPS.js';
20
20
  export { XYZMap } from './model/earth/XYZMap.js';
21
21
  export { WMTSAdapter } from './model/earth/WMTSAdapter.js';
22
+ export { MeshHiPS } from './model/meships/MeshHiPS.js';
23
+ export { MeshHiPSDescriptor } from './model/meships/MeshHiPSDescriptor.js';
22
24
  export type { XYZLayerConfig, WMTSLayerConfig, WMTSRequestEncoding } from './model/earth/XYZConfig.js';
23
25
  export type { XYZTileCoord, XYZTileMesh } from './model/earth/XYZTypes.js';
26
+ export type { MeshHiPSConfig, MeshHiPSDebugStats, MeshHiPSTileCoord } from './model/meships/MeshHiPSTypes.js';
24
27
  export { Source } from './model/Source.js';
25
28
  export { Footprint } from './model/footprints/Footprint.js';
package/lib-esm/index.js CHANGED
@@ -28,6 +28,8 @@ export { ColorMaps, COLOR_MAP_SAMPLE_COUNT, createColorMapFromSamples } from './
28
28
  export { HiPS } from './model/hips/HiPS.js';
29
29
  export { XYZMap } from './model/earth/XYZMap.js';
30
30
  export { WMTSAdapter } from './model/earth/WMTSAdapter.js';
31
+ export { MeshHiPS } from './model/meships/MeshHiPS.js';
32
+ export { MeshHiPSDescriptor } from './model/meships/MeshHiPSDescriptor.js';
31
33
  export { Source } from './model/Source.js';
32
34
  export { Footprint } from './model/footprints/Footprint.js';
33
35
  console.log('astroviewer UMD loaded');
@@ -48,7 +48,7 @@ export type XYZRequestSchedulerDebugStats = {
48
48
  hostsInBackoff: XYZRequestBackoffDebugEntry[];
49
49
  };
50
50
  export type XYZDebugStats = {
51
- activeBaseLayer: 'hips' | 'xyz' | null;
51
+ activeBaseLayer: 'hips' | 'xyz' | 'meships' | null;
52
52
  layer: XYZLayerDebugStats | null;
53
53
  requests: XYZRequestSchedulerDebugStats;
54
54
  };
@@ -1,5 +1,5 @@
1
1
  export type HiPSDebugStats = {
2
- activeBaseLayer: 'hips' | 'xyz' | null;
2
+ activeBaseLayer: 'hips' | 'xyz' | 'meships' | null;
3
3
  hipsName: string | null;
4
4
  hipsUrl: string | null;
5
5
  isGalactic: boolean | null;
@@ -0,0 +1,30 @@
1
+ import { AbstractSkyEntity, SkyEntityDrawInput } from '../AbstractSkyEntity.js';
2
+ import { HealpixGrid } from '../grid/HealpixGrid.js';
3
+ import { MeshHiPSDescriptor } from './MeshHiPSDescriptor.js';
4
+ import type { MeshHiPSDebugStats } from './MeshHiPSTypes.js';
5
+ export declare class MeshHiPS extends AbstractSkyEntity {
6
+ private _descriptor;
7
+ private _healpixGrid;
8
+ private _shaderProgram;
9
+ private _tiles;
10
+ private _currentOrder;
11
+ private _visibleTiles;
12
+ private _coverageTiles;
13
+ constructor(radius: number, position: [number, number, number], xrad: number, yrad: number, _descriptor: MeshHiPSDescriptor, webgl: WebGL2RenderingContext, _healpixGrid: HealpixGrid);
14
+ get currentOrder(): number;
15
+ get maxOrder(): number;
16
+ get minOrder(): number;
17
+ get baseURL(): string;
18
+ get propertiesRawText(): string;
19
+ get properties(): ReadonlyMap<string, string>;
20
+ getProperty(key: string): string | undefined;
21
+ refreshOrder(fovDeg: number | undefined): number;
22
+ draw(input: SkyEntityDrawInput): void;
23
+ getDebugStats(): MeshHiPSDebugStats;
24
+ private resolveVisibleTiles;
25
+ private resolveCoverageTiles;
26
+ private groupTilesByOrder;
27
+ private ensureTiles;
28
+ private evictCached;
29
+ private tileKey;
30
+ }
@@ -0,0 +1,182 @@
1
+ /*
2
+ * AstroViewer
3
+ * Copyright (C) Fabrizio Giordano
4
+ * SPDX-License-Identifier: LicenseRef-AstroViewer-Dual-License
5
+ */
6
+ import { AbstractSkyEntity } from '../AbstractSkyEntity.js';
7
+ import { fovHelper } from '../hips/FoVHelper.js';
8
+ import { MeshHiPSShaderProgram } from '../../shader/MeshHiPSShaderProgram.js';
9
+ import { MeshHiPSTile } from './MeshHiPSTile.js';
10
+ export class MeshHiPS extends AbstractSkyEntity {
11
+ _descriptor;
12
+ _healpixGrid;
13
+ _shaderProgram;
14
+ _tiles = new Map();
15
+ _currentOrder;
16
+ _visibleTiles = [];
17
+ _coverageTiles = [];
18
+ constructor(radius, position, xrad, yrad, _descriptor, webgl, _healpixGrid) {
19
+ super(radius, position, xrad, yrad, _descriptor.name, webgl, false);
20
+ this._descriptor = _descriptor;
21
+ this._healpixGrid = _healpixGrid;
22
+ this.initGL(webgl);
23
+ this._shaderProgram = new MeshHiPSShaderProgram(webgl);
24
+ this._shaderProgram.enableProgram();
25
+ this._currentOrder = _descriptor.selectedOrder;
26
+ }
27
+ get currentOrder() {
28
+ return this._currentOrder;
29
+ }
30
+ get maxOrder() {
31
+ return this._descriptor.maxOrder;
32
+ }
33
+ get minOrder() {
34
+ return this._descriptor.minOrder;
35
+ }
36
+ get baseURL() {
37
+ return this._descriptor.baseUrl;
38
+ }
39
+ get propertiesRawText() {
40
+ return this._descriptor.propertiesRawText;
41
+ }
42
+ get properties() {
43
+ return this._descriptor.properties;
44
+ }
45
+ getProperty(key) {
46
+ return this._descriptor.getProperty(key);
47
+ }
48
+ refreshOrder(fovDeg) {
49
+ if (this._descriptor.fixedOrder) {
50
+ this._currentOrder = this._descriptor.selectedOrder;
51
+ return this._currentOrder;
52
+ }
53
+ const fov = Number.isFinite(fovDeg) && fovDeg > 0
54
+ ? fovDeg
55
+ : this._healpixGrid.getMinFoV();
56
+ const safeFov = Number.isFinite(fov) && fov > 0 ? fov : 1e-6;
57
+ const order = fovHelper.getHiPSNorder(safeFov, this._currentOrder);
58
+ this._currentOrder = Math.max(this._descriptor.minOrder, Math.min(this._descriptor.maxOrder, order));
59
+ return this._currentOrder;
60
+ }
61
+ draw(input) {
62
+ const vMatrix = input.camera.getCameraMatrix();
63
+ if (!vMatrix || !input.pMatrix)
64
+ return;
65
+ this.refreshOrder(input.fovDeg);
66
+ this._visibleTiles = this.resolveVisibleTiles();
67
+ this._coverageTiles = this.resolveCoverageTiles(this._visibleTiles);
68
+ this.ensureTiles(this._coverageTiles);
69
+ this.evictCached();
70
+ const gl = this._webgl;
71
+ const pMatrix = input.pMatrix;
72
+ const mMatrix = this.getModelMatrix();
73
+ const wasCullFace = gl.isEnabled(gl.CULL_FACE);
74
+ const wasDepthTest = gl.isEnabled(gl.DEPTH_TEST);
75
+ const wasDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK);
76
+ const previousDepthFunc = gl.getParameter(gl.DEPTH_FUNC);
77
+ gl.enable(gl.DEPTH_TEST);
78
+ gl.depthMask(true);
79
+ gl.depthFunc(gl.LEQUAL);
80
+ gl.disable(gl.CULL_FACE);
81
+ const byOrder = this.groupTilesByOrder(this._coverageTiles);
82
+ const orders = Array.from(byOrder.keys()).sort((a, b) => a - b);
83
+ for (const order of orders) {
84
+ for (const coord of byOrder.get(order) ?? []) {
85
+ this._tiles.get(this.tileKey(coord))?.draw(pMatrix, vMatrix, mMatrix, this._descriptor.color, this._descriptor.wireframe);
86
+ }
87
+ }
88
+ if (wasCullFace)
89
+ gl.enable(gl.CULL_FACE);
90
+ if (!wasDepthTest)
91
+ gl.disable(gl.DEPTH_TEST);
92
+ gl.depthFunc(previousDepthFunc);
93
+ gl.depthMask(wasDepthMask);
94
+ }
95
+ getDebugStats() {
96
+ const tiles = Array.from(this._tiles.values());
97
+ return {
98
+ activeBaseLayer: 'meships',
99
+ meshHiPSName: this._descriptor.name,
100
+ meshHiPSUrl: this._descriptor.baseUrl,
101
+ currentOrder: this._currentOrder,
102
+ visibleTileCount: this._visibleTiles.length,
103
+ coverageTileCount: this._coverageTiles.length,
104
+ cacheSize: this._tiles.size,
105
+ readyTileCount: tiles.filter((tile) => tile.ready).length,
106
+ loadingTileCount: tiles.filter((tile) => tile.loading).length,
107
+ failedTileCount: tiles.filter((tile) => tile.failed).length,
108
+ };
109
+ }
110
+ resolveVisibleTiles() {
111
+ const manager = this._healpixGrid.visibleTilesManager;
112
+ const byOrder = manager?.visibleTilesByOrder;
113
+ const pixels = byOrder?.order === this._currentOrder && Array.isArray(byOrder.pixels)
114
+ ? byOrder.pixels
115
+ : [];
116
+ if (pixels.length > 0) {
117
+ return pixels.map((ipix) => ({ order: this._currentOrder, ipix }));
118
+ }
119
+ const tileCount = 12 * 4 ** this._currentOrder;
120
+ return Array.from({ length: tileCount }, (_, ipix) => ({ order: this._currentOrder, ipix }));
121
+ }
122
+ resolveCoverageTiles(visibleTiles) {
123
+ const tiles = new Map();
124
+ const add = (coord) => {
125
+ if (coord.order < this._descriptor.minOrder || coord.order > this._descriptor.maxOrder)
126
+ return;
127
+ tiles.set(this.tileKey(coord), coord);
128
+ };
129
+ for (const coord of visibleTiles) {
130
+ add(coord);
131
+ for (let order = coord.order - 1; order >= this._descriptor.minOrder; order--) {
132
+ const shift = 2 * (coord.order - order);
133
+ add({ order, ipix: coord.ipix >> shift });
134
+ }
135
+ }
136
+ const manager = this._healpixGrid.visibleTilesManager;
137
+ const ancestorsMap = manager?.ancestorsMap;
138
+ if (ancestorsMap) {
139
+ for (const [order, ipixes] of ancestorsMap) {
140
+ if (order < this._descriptor.minOrder || order > this._descriptor.maxOrder)
141
+ continue;
142
+ for (const ipix of ipixes)
143
+ add({ order, ipix });
144
+ }
145
+ }
146
+ return Array.from(tiles.values());
147
+ }
148
+ groupTilesByOrder(coords) {
149
+ const byOrder = new Map();
150
+ for (const coord of coords) {
151
+ const list = byOrder.get(coord.order) ?? [];
152
+ list.push(coord);
153
+ byOrder.set(coord.order, list);
154
+ }
155
+ return byOrder;
156
+ }
157
+ ensureTiles(coords) {
158
+ for (const coord of coords) {
159
+ const key = this.tileKey(coord);
160
+ if (this._tiles.has(key))
161
+ continue;
162
+ this._tiles.set(key, new MeshHiPSTile(coord, this._descriptor.getTileUrl(coord.order, coord.ipix), this._webgl, this._shaderProgram));
163
+ }
164
+ }
165
+ evictCached() {
166
+ const maxCached = this._descriptor.maxCachedTiles;
167
+ if (this._tiles.size <= maxCached)
168
+ return;
169
+ const visibleKeys = new Set(this._coverageTiles.map((coord) => this.tileKey(coord)));
170
+ const evictable = Array.from(this._tiles.entries())
171
+ .filter(([key]) => !visibleKeys.has(key))
172
+ .sort((a, b) => a[1].lastUsedAt - b[1].lastUsedAt);
173
+ while (this._tiles.size > maxCached && evictable.length > 0) {
174
+ const [key, tile] = evictable.shift();
175
+ tile.dispose();
176
+ this._tiles.delete(key);
177
+ }
178
+ }
179
+ tileKey(coord) {
180
+ return `${coord.order}/${coord.ipix}`;
181
+ }
182
+ }
@@ -0,0 +1,34 @@
1
+ import type { MeshHiPSConfig } from './MeshHiPSTypes.js';
2
+ export declare class MeshHiPSDescriptor {
3
+ private _name;
4
+ private _baseUrl;
5
+ private _minOrder;
6
+ private _maxOrder;
7
+ private _selectedOrder;
8
+ private _fixedOrder;
9
+ private _maxCachedTiles;
10
+ private _color;
11
+ private _wireframe;
12
+ private _propertiesRawText;
13
+ private _propertiesMap;
14
+ private _meshRadius;
15
+ constructor(config: MeshHiPSConfig, propertiesText?: string);
16
+ get name(): string;
17
+ get baseUrl(): string;
18
+ get minOrder(): number;
19
+ get maxOrder(): number;
20
+ get selectedOrder(): number;
21
+ get fixedOrder(): boolean;
22
+ get maxCachedTiles(): number;
23
+ get color(): [number, number, number, number];
24
+ get wireframe(): boolean;
25
+ get propertiesRawText(): string;
26
+ get properties(): ReadonlyMap<string, string>;
27
+ get meshRadius(): number;
28
+ getProperty(key: string): string | undefined;
29
+ getTileUrl(order: number, ipix: number): string;
30
+ private parseProperties;
31
+ private readNumber;
32
+ private clampOrder;
33
+ private normalizeBaseUrl;
34
+ }
@@ -0,0 +1,132 @@
1
+ /*
2
+ * AstroViewer
3
+ * Copyright (C) Fabrizio Giordano
4
+ * SPDX-License-Identifier: LicenseRef-AstroViewer-Dual-License
5
+ */
6
+ export class MeshHiPSDescriptor {
7
+ _name = 'MeshHiPS';
8
+ _baseUrl;
9
+ _minOrder = 0;
10
+ _maxOrder = 0;
11
+ _selectedOrder;
12
+ _fixedOrder = false;
13
+ _maxCachedTiles = 384;
14
+ // default neutral color (contrasts with page background)
15
+ _color = [0.32, 0.34, 0.36, 1.0];
16
+ _wireframe = false;
17
+ _propertiesRawText = '';
18
+ _propertiesMap = new Map();
19
+ _meshRadius = null;
20
+ constructor(config, propertiesText = '') {
21
+ this._baseUrl = this.normalizeBaseUrl(config.baseUrl);
22
+ this._propertiesRawText = propertiesText;
23
+ this.parseProperties(propertiesText);
24
+ this._name = config.name ?? this._propertiesMap.get('obs_collection') ?? this._propertiesMap.get('label') ?? this._name;
25
+ this._minOrder = config.minOrder ?? this.readNumber('hips_order_min', this._minOrder);
26
+ this._maxOrder = config.maxOrder ?? this.readNumber('hips_order', this.readNumber('hips_order_max', this._maxOrder));
27
+ this._fixedOrder = config.order !== undefined;
28
+ this._selectedOrder = config.order ?? this._minOrder;
29
+ this._maxCachedTiles = config.maxCachedTiles ?? this._maxCachedTiles;
30
+ // color: prefer explicit config, then properties.mesh_color, then default
31
+ if (config.color) {
32
+ this._color = config.color;
33
+ }
34
+ else if (this._propertiesMap.has('mesh_color')) {
35
+ const raw = this._propertiesMap.get('mesh_color') || '';
36
+ const parts = raw.split(/[,\s]+/).map((v) => Number(v)).filter(Number.isFinite);
37
+ if (parts.length >= 3) {
38
+ let [r, g, b, a] = parts;
39
+ if (r > 1 || g > 1 || b > 1) {
40
+ // assume 0-255 range
41
+ r = r / 255;
42
+ g = g / 255;
43
+ b = b / 255;
44
+ }
45
+ if (!Number.isFinite(a))
46
+ a = 1;
47
+ this._color = [r, g, b, a];
48
+ }
49
+ }
50
+ else {
51
+ this._color = this._color;
52
+ }
53
+ this._wireframe = config.wireframe ?? this._wireframe;
54
+ this._selectedOrder = this.clampOrder(this._selectedOrder);
55
+ // mesh radius: prefer explicit value from config, otherwise read mandatory property
56
+ if (Number.isFinite(config.meshRadius)) {
57
+ this._meshRadius = config.meshRadius;
58
+ }
59
+ else {
60
+ const radiusFromProps = this.readNumber('mesh_radius', NaN);
61
+ if (!Number.isFinite(radiusFromProps)) {
62
+ throw new Error('Missing mandatory property "mesh_radius" in MeshHiPS properties');
63
+ }
64
+ this._meshRadius = radiusFromProps;
65
+ }
66
+ }
67
+ get name() {
68
+ return this._name;
69
+ }
70
+ get baseUrl() {
71
+ return this._baseUrl;
72
+ }
73
+ get minOrder() {
74
+ return this._minOrder;
75
+ }
76
+ get maxOrder() {
77
+ return this._maxOrder;
78
+ }
79
+ get selectedOrder() {
80
+ return this._selectedOrder;
81
+ }
82
+ get fixedOrder() {
83
+ return this._fixedOrder;
84
+ }
85
+ get maxCachedTiles() {
86
+ return this._maxCachedTiles;
87
+ }
88
+ get color() {
89
+ return this._color;
90
+ }
91
+ get wireframe() {
92
+ return this._wireframe;
93
+ }
94
+ get propertiesRawText() {
95
+ return this._propertiesRawText;
96
+ }
97
+ get properties() {
98
+ return new Map(this._propertiesMap);
99
+ }
100
+ get meshRadius() {
101
+ return this._meshRadius;
102
+ }
103
+ getProperty(key) {
104
+ return this._propertiesMap.get(key);
105
+ }
106
+ getTileUrl(order, ipix) {
107
+ const dir = Math.floor(ipix / 10_000) * 10_000;
108
+ return `${this._baseUrl}Norder${order}/Dir${dir}/Npix${ipix}.obj`;
109
+ }
110
+ parseProperties(propertiesText) {
111
+ const lines = propertiesText.split(/\r\n|\n/);
112
+ for (const raw of lines) {
113
+ const line = raw.trim();
114
+ if (!line || line.startsWith('#'))
115
+ continue;
116
+ const idx = line.indexOf('=');
117
+ if (idx < 0)
118
+ continue;
119
+ this._propertiesMap.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());
120
+ }
121
+ }
122
+ readNumber(key, fallback) {
123
+ const parsed = Number(this._propertiesMap.get(key));
124
+ return Number.isFinite(parsed) ? parsed : fallback;
125
+ }
126
+ clampOrder(order) {
127
+ return Math.max(this._minOrder, Math.min(this._maxOrder, order));
128
+ }
129
+ normalizeBaseUrl(baseUrl) {
130
+ return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
131
+ }
132
+ }
@@ -0,0 +1,29 @@
1
+ import { MeshHiPSShaderProgram } from '../../shader/MeshHiPSShaderProgram.js';
2
+ import type { MeshHiPSTileCoord } from './MeshHiPSTypes.js';
3
+ type Mat4 = Float32Array;
4
+ export declare class MeshHiPSTile {
5
+ readonly coord: MeshHiPSTileCoord;
6
+ private _url;
7
+ private _webgl;
8
+ private _shaderProgram;
9
+ private _gpuMesh;
10
+ private _ready;
11
+ private _loading;
12
+ private _failed;
13
+ private _lastUsedAt;
14
+ private _createdAt;
15
+ constructor(coord: MeshHiPSTileCoord, _url: string, _webgl: WebGL2RenderingContext, _shaderProgram: MeshHiPSShaderProgram);
16
+ get ready(): boolean;
17
+ get loading(): boolean;
18
+ get failed(): boolean;
19
+ get lastUsedAt(): number;
20
+ get createdAt(): number;
21
+ touch(): void;
22
+ draw(pMatrix: Mat4, vMatrix: Mat4, mMatrix: Mat4, color: [number, number, number, number], wireframe: boolean): boolean;
23
+ dispose(): void;
24
+ private load;
25
+ private uploadMesh;
26
+ private buildLineIndices;
27
+ private get key();
28
+ }
29
+ export {};