astro-viewer 2.0.0 → 3.0.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 (40) hide show
  1. package/dist/astroviewer.cjs +18851 -17175
  2. package/dist/astroviewer.js +1 -1
  3. package/dist/astroviewer.min.js +1 -1
  4. package/lib-esm/AstroSphere.d.ts +9 -0
  5. package/lib-esm/AstroSphere.js +63 -9
  6. package/lib-esm/AstroViewer.d.ts +18 -0
  7. package/lib-esm/AstroViewer.js +46 -0
  8. package/lib-esm/index.d.ts +5 -0
  9. package/lib-esm/index.js +4 -0
  10. package/lib-esm/model/AbstractSkyEntity.d.ts +10 -0
  11. package/lib-esm/model/earth/XYZAncestorMeshCache.d.ts +10 -0
  12. package/lib-esm/model/earth/XYZAncestorMeshCache.js +31 -0
  13. package/lib-esm/model/earth/XYZLayer.d.ts +31 -0
  14. package/lib-esm/model/earth/XYZLayer.js +231 -0
  15. package/lib-esm/model/earth/XYZMeshBuilder.d.ts +6 -0
  16. package/lib-esm/model/earth/XYZMeshBuilder.js +97 -0
  17. package/lib-esm/model/earth/XYZTile.d.ts +39 -0
  18. package/lib-esm/model/earth/XYZTile.js +187 -0
  19. package/lib-esm/model/earth/XYZTileBuffer.d.ts +39 -0
  20. package/lib-esm/model/earth/XYZTileBuffer.js +149 -0
  21. package/lib-esm/model/earth/XYZTileProvider.d.ts +29 -0
  22. package/lib-esm/model/earth/XYZTileProvider.js +187 -0
  23. package/lib-esm/model/earth/XYZTileRequestScheduler.d.ts +25 -0
  24. package/lib-esm/model/earth/XYZTileRequestScheduler.js +161 -0
  25. package/lib-esm/model/earth/XYZVisibleTilesManager.d.ts +30 -0
  26. package/lib-esm/model/earth/XYZVisibleTilesManager.js +208 -0
  27. package/lib-esm/model/earth/types.d.ts +87 -0
  28. package/lib-esm/model/earth/types.js +1 -0
  29. package/lib-esm/model/earth/wmts/WMTSAdapter.d.ts +11 -0
  30. package/lib-esm/model/earth/wmts/WMTSAdapter.js +85 -0
  31. package/lib-esm/model/earth/wmts/types.d.ts +1 -0
  32. package/lib-esm/model/earth/wmts/types.js +1 -0
  33. package/lib-esm/model/footprints/FootprintSetGL.js +1 -1
  34. package/lib-esm/model/terra/TerraFootprintSetGL.d.ts +4 -0
  35. package/lib-esm/model/terra/TerraFootprintSetGL.js +4 -0
  36. package/lib-esm/model/terra/TerraPointSetGL.d.ts +4 -0
  37. package/lib-esm/model/terra/TerraPointSetGL.js +4 -0
  38. package/lib-esm/shader/XYZShaderProgram.d.ts +20 -0
  39. package/lib-esm/shader/XYZShaderProgram.js +88 -0
  40. package/package.json +1 -2
@@ -9,6 +9,8 @@ import { FootprintSetGL, HoveredFootprintDetail } from './model/footprints/Footp
9
9
  import { EquatorialGrid } from './model/grid/EquatorialGrid.js';
10
10
  import { HealpixGrid } from './model/grid/HealpixGrid.js';
11
11
  import { ColorMap } from './model/ColorMaps.js';
12
+ import { XYZLayer } from './model/earth/XYZLayer.js';
13
+ import type { WMTSLayerConfig, XYZDebugStats, XYZLayerConfig } from './model/earth/types.js';
12
14
  export type PointCoordinates = {
13
15
  astroDeg: AstroCoords;
14
16
  raHMS: HMS;
@@ -54,6 +56,8 @@ declare class AstroSphere {
54
56
  private pointerDownY;
55
57
  private pointerDownAt;
56
58
  private _activeHiPS;
59
+ private _activeXYZ;
60
+ private _activeBaseLayer;
57
61
  private startup;
58
62
  private fov;
59
63
  private activeCatalogues;
@@ -83,7 +87,10 @@ declare class AstroSphere {
83
87
  private emitCameraChanged;
84
88
  private addEventListeners;
85
89
  getPhiThetaDeg(canvas: HTMLCanvasElement): SphericalCoords;
90
+ private collectViewportSphericalSamples;
86
91
  activateHiPS(hipsDescriptor: HiPSDescriptor): void;
92
+ activateXYZ(config: XYZLayerConfig): void;
93
+ activateWMTS(config: WMTSLayerConfig): void;
87
94
  showCatalogue(cat: CatalogueGL): Promise<CatalogueGL>;
88
95
  deleteCatalogue(catalogue: CatalogueGL): void;
89
96
  showFootprintSet(fset: FootprintSetGL): Promise<FootprintSetGL>;
@@ -107,6 +114,8 @@ declare class AstroSphere {
107
114
  private prevCentralRaDeg;
108
115
  private prevCentralDecDeg;
109
116
  get activeHiPS(): HiPS | null;
117
+ get activeXYZ(): XYZLayer | null;
118
+ getXYZDebugStats(): XYZDebugStats;
110
119
  draw(canvas: HTMLCanvasElement): void;
111
120
  private emitHoveredSourceIfChanged;
112
121
  }
@@ -12,6 +12,9 @@ import { EquatorialGrid } from './model/grid/EquatorialGrid.js';
12
12
  import { HealpixGrid } from './model/grid/HealpixGrid.js';
13
13
  import { CoordsType } from './utils/CoordsType.js';
14
14
  import ColorMaps from './model/ColorMaps.js';
15
+ import { XYZLayer } from './model/earth/XYZLayer.js';
16
+ import { xyzTileRequestScheduler } from './model/earth/XYZTileRequestScheduler.js';
17
+ import { WMTSAdapter } from './model/earth/wmts/WMTSAdapter.js';
15
18
  /**
16
19
  * AstroSphere — main WebGL scene controller (TS port)
17
20
  */
@@ -36,6 +39,8 @@ class AstroSphere {
36
39
  pointerDownY = null;
37
40
  pointerDownAt = 0;
38
41
  _activeHiPS = null;
42
+ _activeXYZ = null;
43
+ _activeBaseLayer = null;
39
44
  startup = true;
40
45
  fov;
41
46
  activeCatalogues = [];
@@ -392,8 +397,35 @@ class AstroSphere {
392
397
  const pickerPoint = RayPickingUtils.getIntersectionPointWithSingleModel(maxX / 2, maxY / 2, this._healpixGrid, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
393
398
  return cartesianToSpherical(pickerPoint);
394
399
  }
400
+ collectViewportSphericalSamples(sampleCount = 5) {
401
+ const rect = this.canvas.getBoundingClientRect();
402
+ const width = rect.width;
403
+ const height = rect.height;
404
+ const samples = [];
405
+ for (let ix = 0; ix < sampleCount; ix++) {
406
+ const x = sampleCount === 1 ? width / 2 : (ix / (sampleCount - 1)) * width;
407
+ for (let iy = 0; iy < sampleCount; iy++) {
408
+ const y = sampleCount === 1 ? height / 2 : (iy / (sampleCount - 1)) * height;
409
+ const hit = RayPickingUtils.getIntersectionPointWithSingleModel(x, y, this._healpixGrid, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
410
+ if (hit && hit.length > 0) {
411
+ samples.push(cartesianToSpherical(hit));
412
+ }
413
+ }
414
+ }
415
+ return samples;
416
+ }
395
417
  activateHiPS(hipsDescriptor) {
396
418
  this._activeHiPS = new HiPS(1, [0.0, 0.0, 0.0], 0, 0, hipsDescriptor, this._webgl, this._healpixGrid);
419
+ this._activeBaseLayer = 'hips';
420
+ }
421
+ activateXYZ(config) {
422
+ this._activeXYZ = new XYZLayer(config, this._webgl);
423
+ this._activeBaseLayer = 'xyz';
424
+ }
425
+ activateWMTS(config) {
426
+ const adapter = new WMTSAdapter(config);
427
+ this._activeXYZ = new XYZLayer(adapter.toXYZLayerConfig(), this._webgl);
428
+ this._activeBaseLayer = 'xyz';
397
429
  }
398
430
  // Catalogue section
399
431
  async showCatalogue(cat) {
@@ -524,12 +556,22 @@ class AstroSphere {
524
556
  get activeHiPS() {
525
557
  return this._activeHiPS;
526
558
  }
559
+ get activeXYZ() {
560
+ return this._activeXYZ;
561
+ }
562
+ getXYZDebugStats() {
563
+ return {
564
+ activeBaseLayer: this._activeBaseLayer,
565
+ layer: this._activeXYZ?.getDebugStats() ?? null,
566
+ requests: xyzTileRequestScheduler.getDebugStats(),
567
+ };
568
+ }
527
569
  draw(canvas) {
528
570
  if (this._refreshingStatus)
529
571
  return;
530
572
  if (!this._webgl)
531
573
  return;
532
- if (!this._activeHiPS)
574
+ if (!this._activeHiPS && !this._activeXYZ)
533
575
  return;
534
576
  if (!this._healpixGrid || Object.keys(this._healpixGrid).length === 0)
535
577
  return;
@@ -613,15 +655,25 @@ class AstroSphere {
613
655
  this._webgl.enable(this._webgl.CULL_FACE);
614
656
  this._webgl.cullFace(global.insideSphere ? this._webgl.FRONT : this._webgl.BACK);
615
657
  this._webgl.blendFunc(this._webgl.SRC_ALPHA, this._webgl.ONE_MINUS_SRC_ALPHA);
616
- const visibleOrder = Math.min(this._healpixGrid.visibleorder, this._activeHiPS.maxOrder);
617
- this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
658
+ if (this._activeBaseLayer === 'hips' && this._activeHiPS) {
659
+ const visibleOrder = Math.min(this._healpixGrid.visibleorder, this._activeHiPS.maxOrder);
660
+ this._healpixGrid.visibleTilesManager.computeVisiblePixels(visibleOrder, this._webgl, this._camera, this._perspectiveMatrixManager.pMatrix);
661
+ }
618
662
  // DRAW HiPS
619
663
  const skyEntityDrawInput = {
620
664
  fovDeg: this._healpixGrid.getMinFoV(),
621
665
  camera: this._camera,
622
- pMatrix: this._perspectiveMatrixManager.pMatrix
666
+ pMatrix: this._perspectiveMatrixManager.pMatrix,
667
+ centerSphericalDeg: this.updateCentralPoint().sphericalDeg,
668
+ fovPolygon: this._activeBaseLayer === 'xyz' ? this.getFoVPolygon() : undefined,
669
+ viewportSphericalSamples: this._activeBaseLayer === 'xyz' ? this.collectViewportSphericalSamples(7) : undefined,
623
670
  };
624
- this._activeHiPS.draw(skyEntityDrawInput);
671
+ if (this._activeBaseLayer === 'hips') {
672
+ this._activeHiPS?.draw(skyEntityDrawInput);
673
+ }
674
+ if (this._activeBaseLayer === 'xyz') {
675
+ this._activeXYZ?.draw(skyEntityDrawInput);
676
+ }
625
677
  this._healpixGrid.draw(skyEntityDrawInput);
626
678
  this._equatorialGrid.draw(skyEntityDrawInput);
627
679
  this._webgl.enable(this._webgl.DEPTH_TEST);
@@ -642,14 +694,16 @@ class AstroSphere {
642
694
  });
643
695
  }
644
696
  this.activeCatalogues.forEach(cat => {
645
- if (this._activeHiPS) {
646
- cat.draw(this._activeHiPS.getModelMatrix(), this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
697
+ const activeModelMatrix = this._activeHiPS?.getModelMatrix() ?? this._activeXYZ?.getModelMatrix();
698
+ if (activeModelMatrix) {
699
+ cat.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
647
700
  }
648
701
  });
649
702
  this.emitHoveredSourceIfChanged();
650
703
  this.activeFootprintSets.forEach(fst => {
651
- if (this._activeHiPS) {
652
- fst.draw(this._activeHiPS.getModelMatrix(), this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
704
+ const activeModelMatrix = this._activeHiPS?.getModelMatrix() ?? this._activeXYZ?.getModelMatrix();
705
+ if (activeModelMatrix) {
706
+ fst.draw(activeModelMatrix, this.mouseHelper, this._camera.getCameraMatrix(), this._perspectiveMatrixManager.pMatrix);
653
707
  }
654
708
  });
655
709
  }
@@ -9,6 +9,10 @@ import Camera from './Camera.js';
9
9
  import { ReadonlyMat4 } from 'gl-matrix';
10
10
  import { ColorMap, ColorMapName } from './model/ColorMaps.js';
11
11
  import { HiPS } from './model/hips/HiPS.js';
12
+ import { XYZLayer } from './model/earth/XYZLayer.js';
13
+ import type { WMTSLayerConfig, XYZDebugStats, XYZLayerConfig } from './model/earth/types.js';
14
+ import { TerraPointSetGL } from './model/terra/TerraPointSetGL.js';
15
+ import { TerraFootprintSetGL } from './model/terra/TerraFootprintSetGL.js';
12
16
  export declare class AstroViewer {
13
17
  private astroSphere;
14
18
  private canvas;
@@ -32,14 +36,28 @@ export declare class AstroViewer {
32
36
  showFootprintSet(footprintSet: FootprintSetGL): void;
33
37
  hideFootprintSet(footprintSet: FootprintSetGL, isVisible: boolean): void;
34
38
  deleteFootprintSet(footprintSet: FootprintSetGL): void;
39
+ createTerraPointSet(pointSetName: string, pointSetDescription: string, providerUrl: string, metadataManager: MetadataManager): TerraPointSetGL;
40
+ showTerraPointSet(pointSet: TerraPointSetGL): void;
41
+ hideTerraPointSet(pointSet: TerraPointSetGL, isVisible: boolean): void;
42
+ deleteTerraPointSet(pointSet: TerraPointSetGL): void;
43
+ createTerraFootprintSet(footprintSetName: string, footprintSetDescription: string, providerUrl: string, metadataManager: MetadataManager): TerraFootprintSetGL;
44
+ showTerraFootprintSet(footprintSet: TerraFootprintSetGL): void;
45
+ hideTerraFootprintSet(footprintSet: TerraFootprintSetGL, isVisible: boolean): void;
46
+ deleteTerraFootprintSet(footprintSet: TerraFootprintSetGL): void;
35
47
  changeFootprintSetColor(footprintSet: FootprintSetGL, hexColor: string): void;
36
48
  getHoveredFootprints(): HoveredFootprintDetail[];
37
49
  getDefaultHiPSURL(): string;
38
50
  activateHiPS(hipsDescriptor: HiPSDescriptor): void;
51
+ activateXYZ(config: XYZLayerConfig): void;
52
+ activateWMTS(config: WMTSLayerConfig): void;
53
+ setXYZMaxConcurrentRequests(value: number): void;
54
+ getXYZMaxConcurrentRequests(): number;
55
+ getXYZDebugStats(): XYZDebugStats;
39
56
  loadHiPS(baseUrl: string): Promise<string>;
40
57
  changeColorMap(colorMapName: ColorMapName): void;
41
58
  changeCustomColorMap(colorMap: ColorMap): void;
42
59
  getActiveHiPS(): HiPS | null;
60
+ getActiveXYZ(): XYZLayer | null;
43
61
  setCamera(camera: Camera): void;
44
62
  setCameraPosition(pos: [number, number, number]): void;
45
63
  setCameraMatrix(viewMatrix: Float32Array): void;
@@ -5,6 +5,9 @@ import { CatalogueGL } from './model/catalogues/CatalogueGL.js';
5
5
  import { FootprintSetGL } from './model/footprints/FootprintSetGL.js';
6
6
  import { bootSetup } from './Config.js';
7
7
  import ColorMaps from './model/ColorMaps.js';
8
+ import { xyzTileRequestScheduler } from './model/earth/XYZTileRequestScheduler.js';
9
+ import { TerraPointSetGL } from './model/terra/TerraPointSetGL.js';
10
+ import { TerraFootprintSetGL } from './model/terra/TerraFootprintSetGL.js';
8
11
  // & {
9
12
  // viewportWidth: number
10
13
  // viewportHeight: number
@@ -71,6 +74,31 @@ export class AstroViewer {
71
74
  deleteFootprintSet(footprintSet) {
72
75
  this.astroSphere.deleteFootprintSet(footprintSet);
73
76
  }
77
+ // TERRA OVERLAYS
78
+ createTerraPointSet(pointSetName, pointSetDescription, providerUrl, metadataManager) {
79
+ return new TerraPointSetGL(pointSetName, pointSetDescription, providerUrl, metadataManager, this.webgl, this.astroSphere.healpixGrid.visibleTilesManager);
80
+ }
81
+ showTerraPointSet(pointSet) {
82
+ this.astroSphere.showCatalogue(pointSet);
83
+ }
84
+ hideTerraPointSet(pointSet, isVisible) {
85
+ pointSet.setIsVisible(isVisible);
86
+ }
87
+ deleteTerraPointSet(pointSet) {
88
+ this.astroSphere.deleteCatalogue(pointSet);
89
+ }
90
+ createTerraFootprintSet(footprintSetName, footprintSetDescription, providerUrl, metadataManager) {
91
+ return new TerraFootprintSetGL(footprintSetName, footprintSetDescription, providerUrl, metadataManager, this.webgl, this.astroSphere.healpixGrid.visibleTilesManager);
92
+ }
93
+ showTerraFootprintSet(footprintSet) {
94
+ this.astroSphere.showFootprintSet(footprintSet);
95
+ }
96
+ hideTerraFootprintSet(footprintSet, isVisible) {
97
+ footprintSet.setIsVisible(isVisible);
98
+ }
99
+ deleteTerraFootprintSet(footprintSet) {
100
+ this.astroSphere.deleteFootprintSet(footprintSet);
101
+ }
74
102
  changeFootprintSetColor(footprintSet, hexColor) {
75
103
  // footprintSet.footprintsetProps.changeColor(hexColor)
76
104
  footprintSet.changeColor(hexColor);
@@ -85,6 +113,21 @@ export class AstroViewer {
85
113
  activateHiPS(hipsDescriptor) {
86
114
  this.astroSphere.activateHiPS(hipsDescriptor);
87
115
  }
116
+ activateXYZ(config) {
117
+ this.astroSphere.activateXYZ(config);
118
+ }
119
+ activateWMTS(config) {
120
+ this.astroSphere.activateWMTS(config);
121
+ }
122
+ setXYZMaxConcurrentRequests(value) {
123
+ xyzTileRequestScheduler.setMaxConcurrent(value);
124
+ }
125
+ getXYZMaxConcurrentRequests() {
126
+ return xyzTileRequestScheduler.getMaxConcurrent();
127
+ }
128
+ getXYZDebugStats() {
129
+ return this.astroSphere.getXYZDebugStats();
130
+ }
88
131
  async loadHiPS(baseUrl) {
89
132
  const hipsUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
90
133
  const resp = await fetch(hipsUrl + 'properties');
@@ -108,6 +151,9 @@ export class AstroViewer {
108
151
  getActiveHiPS() {
109
152
  return this.astroSphere.activeHiPS;
110
153
  }
154
+ getActiveXYZ() {
155
+ return this.astroSphere.activeXYZ;
156
+ }
111
157
  // Camera: GOTOs and COORDS
112
158
  setCamera(camera) {
113
159
  this.astroSphere.setCamera(camera);
@@ -4,6 +4,8 @@ export { FoV } from './model/FoV.js';
4
4
  export type { PointCoordinates, CameraChangedDetail } from './AstroSphere.js';
5
5
  export { HoveredFootprintDetail, FootprintSetGL } from './model/footprints/FootprintSetGL.js';
6
6
  export { CatalogueGL } from './model/catalogues/CatalogueGL.js';
7
+ export { TerraPointSetGL } from './model/terra/TerraPointSetGL.js';
8
+ export { TerraFootprintSetGL } from './model/terra/TerraFootprintSetGL.js';
7
9
  export { MetadataManager } from './model/MetadataManager.js';
8
10
  export { MetadataColumn, MetadataInit, ColumnType } from './model/MetadataColumn.js';
9
11
  export { Point, SphericalOpts, AstroOpts, PointInitOpts, CartesianOpts } from './model/Point.js';
@@ -12,5 +14,8 @@ export { CoordsType } from './utils/CoordsType.js';
12
14
  export { ColorMaps, COLOR_MAP_SAMPLE_COUNT, createColorMapFromSamples } from './model/ColorMaps.js';
13
15
  export type { ColorMap, ColorMapName } from './model/ColorMaps.js';
14
16
  export { HiPS } from './model/hips/HiPS.js';
17
+ export { XYZLayer } from './model/earth/XYZLayer.js';
18
+ export { WMTSAdapter } from './model/earth/wmts/WMTSAdapter.js';
19
+ export type { XYZLayerConfig, XYZTileCoord, XYZTileMesh, WMTSLayerConfig, WMTSRequestEncoding } from './model/earth/types.js';
15
20
  export { Source } from './model/Source.js';
16
21
  export { Footprint } from './model/footprints/Footprint.js';
package/lib-esm/index.js CHANGED
@@ -3,6 +3,8 @@ export { HiPSDescriptor } from './model/hips/HiPSDescriptor.js';
3
3
  export { FoV } from './model/FoV.js';
4
4
  export { FootprintSetGL } from './model/footprints/FootprintSetGL.js';
5
5
  export { CatalogueGL } from './model/catalogues/CatalogueGL.js';
6
+ export { TerraPointSetGL } from './model/terra/TerraPointSetGL.js';
7
+ export { TerraFootprintSetGL } from './model/terra/TerraFootprintSetGL.js';
6
8
  export { MetadataManager } from './model/MetadataManager.js';
7
9
  export { MetadataColumn, ColumnType } from './model/MetadataColumn.js';
8
10
  export { Point } from './model/Point.js';
@@ -10,6 +12,8 @@ export { FoVUtils } from './utils/FoVUtils.js';
10
12
  export { CoordsType } from './utils/CoordsType.js';
11
13
  export { ColorMaps, COLOR_MAP_SAMPLE_COUNT, createColorMapFromSamples } from './model/ColorMaps.js';
12
14
  export { HiPS } from './model/hips/HiPS.js';
15
+ export { XYZLayer } from './model/earth/XYZLayer.js';
16
+ export { WMTSAdapter } from './model/earth/wmts/WMTSAdapter.js';
13
17
  export { Source } from './model/Source.js';
14
18
  export { Footprint } from './model/footprints/Footprint.js';
15
19
  console.log('astroviewer UMD loaded');
@@ -4,11 +4,21 @@
4
4
  import { vec3, mat4, ReadonlyVec3, ReadonlyMat4 } from "gl-matrix";
5
5
  import { HiPSShaderProgram } from '../shader/HiPSShaderProgram.js';
6
6
  import Camera from "../Camera.js";
7
+ import { Point } from "./Point.js";
7
8
  type GL = WebGLRenderingContext | WebGL2RenderingContext;
8
9
  export interface SkyEntityDrawInput {
9
10
  fovDeg?: number;
10
11
  camera: Camera;
11
12
  pMatrix: ReadonlyMat4;
13
+ centerSphericalDeg?: {
14
+ phi: number;
15
+ theta: number;
16
+ };
17
+ fovPolygon?: Point[];
18
+ viewportSphericalSamples?: Array<{
19
+ phi: number;
20
+ theta: number;
21
+ }>;
12
22
  }
13
23
  export declare abstract class AbstractSkyEntity {
14
24
  refreshMe: boolean;
@@ -0,0 +1,10 @@
1
+ import type { XYZTileCoord, XYZTileGpuMesh } from './types.js';
2
+ import { XYZMeshBuilder } from './XYZMeshBuilder.js';
3
+ export declare class XYZAncestorMeshCache {
4
+ private _webgl;
5
+ private _meshBuilder;
6
+ private _meshes;
7
+ constructor(webgl: WebGL2RenderingContext, meshBuilder: XYZMeshBuilder);
8
+ getMesh(targetTile: XYZTileCoord, ancestorTile: XYZTileCoord, segmentsPerSide: number): XYZTileGpuMesh;
9
+ dispose(): void;
10
+ }
@@ -0,0 +1,31 @@
1
+ export class XYZAncestorMeshCache {
2
+ _webgl;
3
+ _meshBuilder;
4
+ _meshes = new Map();
5
+ constructor(webgl, meshBuilder) {
6
+ this._webgl = webgl;
7
+ this._meshBuilder = meshBuilder;
8
+ }
9
+ getMesh(targetTile, ancestorTile, segmentsPerSide) {
10
+ const key = `${targetTile.z}/${targetTile.x}/${targetTile.y}->${ancestorTile.z}/${ancestorTile.x}/${ancestorTile.y}@${segmentsPerSide}`;
11
+ const existing = this._meshes.get(key);
12
+ if (existing) {
13
+ return existing;
14
+ }
15
+ const mesh = this._meshBuilder.buildAncestorMesh(targetTile, ancestorTile, segmentsPerSide);
16
+ const uploaded = this._meshBuilder.uploadMesh(mesh, this._webgl);
17
+ this._meshes.set(key, uploaded);
18
+ return uploaded;
19
+ }
20
+ dispose() {
21
+ for (const mesh of this._meshes.values()) {
22
+ if (mesh.positionBuffer)
23
+ this._webgl.deleteBuffer(mesh.positionBuffer);
24
+ if (mesh.uvBuffer)
25
+ this._webgl.deleteBuffer(mesh.uvBuffer);
26
+ if (mesh.indexBuffer)
27
+ this._webgl.deleteBuffer(mesh.indexBuffer);
28
+ }
29
+ this._meshes.clear();
30
+ }
31
+ }
@@ -0,0 +1,31 @@
1
+ import { AbstractSkyEntity, SkyEntityDrawInput } from '../AbstractSkyEntity.js';
2
+ import type { XYZLayerConfig, XYZLayerDebugStats } from './types.js';
3
+ export declare class XYZLayer extends AbstractSkyEntity {
4
+ private static readonly DEFAULT_MAX_CACHED_TILES;
5
+ private _config;
6
+ private _provider;
7
+ private _visibleTilesManager;
8
+ private _meshBuilder;
9
+ private _xyzShaderProgram;
10
+ private _tileBuffer;
11
+ private _ancestorMeshCache;
12
+ private _visibleTileKeys;
13
+ private _fallbackVisibleTileKeys;
14
+ private _baseVisibleTileKeys;
15
+ private _tilePriorities;
16
+ private _tileSelectionKey;
17
+ private _currentTileCount;
18
+ private _fallbackTileCount;
19
+ private _coreTileCount;
20
+ private _coverageTileCount;
21
+ constructor(config: XYZLayerConfig, webgl: WebGL2RenderingContext);
22
+ get config(): XYZLayerConfig;
23
+ getDebugStats(): XYZLayerDebugStats;
24
+ private bootstrapTiles;
25
+ draw(input: SkyEntityDrawInput): void;
26
+ private evictCache;
27
+ private disposeTiles;
28
+ private getTileKey;
29
+ private parseTileKey;
30
+ private findBestAvailableAncestor;
31
+ }
@@ -0,0 +1,231 @@
1
+ import { AbstractSkyEntity } from '../AbstractSkyEntity.js';
2
+ import { XYZShaderProgram } from '../../shader/XYZShaderProgram.js';
3
+ import { XYZAncestorMeshCache } from './XYZAncestorMeshCache.js';
4
+ import { XYZMeshBuilder } from './XYZMeshBuilder.js';
5
+ import { XYZTileBuffer } from './XYZTileBuffer.js';
6
+ import { XYZTileProvider } from './XYZTileProvider.js';
7
+ import { XYZVisibleTilesManager } from './XYZVisibleTilesManager.js';
8
+ export class XYZLayer extends AbstractSkyEntity {
9
+ static DEFAULT_MAX_CACHED_TILES = 384;
10
+ _config;
11
+ _provider;
12
+ _visibleTilesManager;
13
+ _meshBuilder;
14
+ _xyzShaderProgram;
15
+ _tileBuffer;
16
+ _ancestorMeshCache;
17
+ _visibleTileKeys = [];
18
+ _fallbackVisibleTileKeys = [];
19
+ _baseVisibleTileKeys = [];
20
+ _tilePriorities = new Map();
21
+ _tileSelectionKey = null;
22
+ _currentTileCount = 0;
23
+ _fallbackTileCount = 0;
24
+ _coreTileCount = 0;
25
+ _coverageTileCount = 0;
26
+ constructor(config, webgl) {
27
+ super(1, [0, 0, 0], 0, 0, 'XYZ Earth Layer', webgl, false);
28
+ this._config = config;
29
+ this._provider = new XYZTileProvider(config);
30
+ this._visibleTilesManager = new XYZVisibleTilesManager(this._provider);
31
+ this._meshBuilder = new XYZMeshBuilder();
32
+ this._xyzShaderProgram = new XYZShaderProgram(webgl);
33
+ this._tileBuffer = new XYZTileBuffer(1, webgl, this._meshBuilder, this._xyzShaderProgram);
34
+ this._ancestorMeshCache = new XYZAncestorMeshCache(webgl, this._meshBuilder);
35
+ this.initGL(webgl);
36
+ this.bootstrapTiles(180, null, null);
37
+ }
38
+ get config() {
39
+ return this._config;
40
+ }
41
+ getDebugStats() {
42
+ let readyTileCount = 0;
43
+ let loadingTileCount = 0;
44
+ let coolingDownTileCount = 0;
45
+ const now = Date.now();
46
+ const allTiles = [
47
+ ...Array.from(this._tileBuffer.activeTiles.values(), (entry) => entry.tile),
48
+ ...Array.from(this._tileBuffer.cachedTiles.values(), (entry) => entry.tile),
49
+ ];
50
+ for (const tile of allTiles) {
51
+ if (tile.ready) {
52
+ readyTileCount += 1;
53
+ }
54
+ if (tile.loading) {
55
+ loadingTileCount += 1;
56
+ }
57
+ if (tile.failedUntil > now) {
58
+ coolingDownTileCount += 1;
59
+ }
60
+ }
61
+ const currentZoom = this._visibleTileKeys.reduce((maxZoom, tileKey) => {
62
+ const zoom = Number.parseInt(tileKey.split('/')[0] ?? '', 10);
63
+ if (!Number.isFinite(zoom)) {
64
+ return maxZoom;
65
+ }
66
+ return maxZoom == null ? zoom : Math.max(maxZoom, zoom);
67
+ }, null);
68
+ return {
69
+ cacheSize: this._tileBuffer.size,
70
+ visibleTileCount: this._visibleTileKeys.length,
71
+ currentTileCount: this._currentTileCount,
72
+ fallbackTileCount: this._fallbackTileCount,
73
+ coreTileCount: this._coreTileCount,
74
+ coverageTileCount: this._coverageTileCount,
75
+ readyTileCount,
76
+ loadingTileCount,
77
+ coolingDownTileCount,
78
+ currentZoom,
79
+ tileSelectionKey: this._tileSelectionKey,
80
+ isSettling: false,
81
+ coarseTileCount: 0,
82
+ hasPendingSelection: false,
83
+ pendingSelectionKey: null,
84
+ };
85
+ }
86
+ bootstrapTiles(fovDeg, camera, centerSphericalDeg, fovPolygon = null, viewportSphericalSamples = null) {
87
+ const selection = camera
88
+ ? this._visibleTilesManager.selectTiles({
89
+ fovDeg,
90
+ camera,
91
+ pMatrix: new Float32Array(),
92
+ centerSphericalDeg: centerSphericalDeg ?? undefined,
93
+ fovPolygon: fovPolygon ?? undefined,
94
+ viewportSphericalSamples: viewportSphericalSamples ?? undefined,
95
+ })
96
+ : {
97
+ key: 'initial',
98
+ currentTiles: this._provider.getInitialTiles(),
99
+ fallbackTiles: [],
100
+ currentZoom: 1,
101
+ coreTileCount: this._provider.getInitialTiles().length,
102
+ coverageTileCount: 0,
103
+ };
104
+ if (selection.key === this._tileSelectionKey) {
105
+ return;
106
+ }
107
+ this._tileSelectionKey = selection.key;
108
+ this._currentTileCount = selection.currentTiles.length;
109
+ this._fallbackTileCount = selection.fallbackTiles.length;
110
+ this._coreTileCount = selection.coreTileCount;
111
+ this._coverageTileCount = selection.coverageTileCount;
112
+ const segments = this._config.segmentsPerSide ?? 16;
113
+ const baseTiles = this._provider.getInitialTiles();
114
+ const coreTileKeys = new Set(selection.currentTiles
115
+ .slice(0, selection.coreTileCount)
116
+ .map((tileCoord) => this.getTileKey(tileCoord)));
117
+ const baseTileKeys = new Set(baseTiles.map((tileCoord) => this.getTileKey(tileCoord)));
118
+ const prioritizedBaseTiles = baseTiles.map((tileCoord, index) => ({
119
+ tileCoord,
120
+ priority: 5000 + (baseTiles.length - index),
121
+ role: 'base',
122
+ }));
123
+ const prioritizedCurrentTiles = selection.currentTiles.map((tileCoord, index) => ({
124
+ tileCoord,
125
+ priority: 10000 + (selection.currentTiles.length - index),
126
+ role: coreTileKeys.has(this.getTileKey(tileCoord)) ? 'current' : 'coverage',
127
+ }));
128
+ const prioritizedFallbackTiles = selection.fallbackTiles.map((tileCoord, index) => ({
129
+ tileCoord,
130
+ priority: 1000 + (selection.fallbackTiles.length - index),
131
+ role: 'fallback',
132
+ }));
133
+ const requestedTiles = [...prioritizedBaseTiles, ...prioritizedCurrentTiles, ...prioritizedFallbackTiles];
134
+ this._tilePriorities.clear();
135
+ this._fallbackVisibleTileKeys = [];
136
+ this._baseVisibleTileKeys = [];
137
+ const orderedRequests = requestedTiles
138
+ .sort((a, b) => a.tileCoord.z - b.tileCoord.z)
139
+ .map(({ tileCoord, priority, role }) => {
140
+ const tileKey = this.getTileKey(tileCoord);
141
+ this._tilePriorities.set(tileKey, priority);
142
+ return {
143
+ tileCoord,
144
+ priority,
145
+ url: this._provider.getTileUrl(tileCoord),
146
+ role,
147
+ };
148
+ });
149
+ const ensuredKeys = this._tileBuffer.ensureTiles(orderedRequests, segments);
150
+ const fallbackKeySet = new Set(selection.fallbackTiles.map((tileCoord) => this.getTileKey(tileCoord)));
151
+ this._baseVisibleTileKeys = ensuredKeys.filter((tileKey) => baseTileKeys.has(tileKey));
152
+ this._fallbackVisibleTileKeys = ensuredKeys.filter((tileKey) => fallbackKeySet.has(tileKey) && !baseTileKeys.has(tileKey));
153
+ this._visibleTileKeys = ensuredKeys.filter((tileKey) => !fallbackKeySet.has(tileKey) && !baseTileKeys.has(tileKey));
154
+ this.evictCache();
155
+ }
156
+ draw(input) {
157
+ const vMatrix = input.camera.getCameraMatrix();
158
+ if (!vMatrix)
159
+ return;
160
+ this.bootstrapTiles(input.fovDeg ?? 180, input.camera, input.centerSphericalDeg ?? null, input.fovPolygon ?? null, input.viewportSphericalSamples ?? null);
161
+ const pMatrix = input.pMatrix;
162
+ const mMatrix = this.getModelMatrix();
163
+ for (const tileKey of this._baseVisibleTileKeys) {
164
+ const tile = this._tileBuffer.getActiveTile(tileKey);
165
+ if (!tile)
166
+ continue;
167
+ tile.draw(pMatrix, vMatrix, mMatrix, this._tilePriorities.get(tileKey) ?? 0);
168
+ }
169
+ for (const tileKey of this._fallbackVisibleTileKeys) {
170
+ const tile = this._tileBuffer.getActiveTile(tileKey);
171
+ if (!tile)
172
+ continue;
173
+ tile.draw(pMatrix, vMatrix, mMatrix, this._tilePriorities.get(tileKey) ?? 0, false);
174
+ }
175
+ for (const tileKey of this._visibleTileKeys) {
176
+ const tile = this._tileBuffer.getActiveTile(tileKey);
177
+ if (tile?.ready) {
178
+ tile.draw(pMatrix, vMatrix, mMatrix, this._tilePriorities.get(tileKey) ?? 0);
179
+ continue;
180
+ }
181
+ const targetCoord = this.parseTileKey(tileKey);
182
+ if (targetCoord) {
183
+ const ancestorTile = this.findBestAvailableAncestor(targetCoord);
184
+ if (ancestorTile) {
185
+ const ancestorMesh = this._ancestorMeshCache.getMesh(targetCoord, ancestorTile.coord, this._config.segmentsPerSide ?? 16);
186
+ ancestorTile.drawRemapped(ancestorMesh, pMatrix, vMatrix, mMatrix);
187
+ }
188
+ }
189
+ tile?.draw(pMatrix, vMatrix, mMatrix, this._tilePriorities.get(tileKey) ?? 0);
190
+ }
191
+ }
192
+ evictCache() {
193
+ const maxCachedTiles = this._config.maxCachedTiles ?? XYZLayer.DEFAULT_MAX_CACHED_TILES;
194
+ this._tileBuffer.evictCached(maxCachedTiles);
195
+ }
196
+ disposeTiles() {
197
+ this._tileBuffer.dispose();
198
+ this._ancestorMeshCache.dispose();
199
+ this._visibleTileKeys = [];
200
+ this._fallbackVisibleTileKeys = [];
201
+ this._baseVisibleTileKeys = [];
202
+ }
203
+ getTileKey(tileCoord) {
204
+ return `${tileCoord.z}/${tileCoord.x}/${tileCoord.y}`;
205
+ }
206
+ parseTileKey(tileKey) {
207
+ const [zRaw, xRaw, yRaw] = tileKey.split('/');
208
+ const z = Number.parseInt(zRaw ?? '', 10);
209
+ const x = Number.parseInt(xRaw ?? '', 10);
210
+ const y = Number.parseInt(yRaw ?? '', 10);
211
+ if (!Number.isFinite(z) || !Number.isFinite(x) || !Number.isFinite(y)) {
212
+ return null;
213
+ }
214
+ return { z, x, y };
215
+ }
216
+ findBestAvailableAncestor(targetCoord) {
217
+ for (let z = targetCoord.z - 1; z >= this._provider.minZoom; z--) {
218
+ const dz = targetCoord.z - z;
219
+ const ancestorCoord = {
220
+ z,
221
+ x: targetCoord.x >> dz,
222
+ y: targetCoord.y >> dz,
223
+ };
224
+ const ancestor = this._tileBuffer.getAnyTile(this.getTileKey(ancestorCoord));
225
+ if (ancestor?.ready) {
226
+ return ancestor;
227
+ }
228
+ }
229
+ return null;
230
+ }
231
+ }
@@ -0,0 +1,6 @@
1
+ import type { XYZTileCoord, XYZTileMesh, XYZTileGpuMesh } from './types.js';
2
+ export declare class XYZMeshBuilder {
3
+ buildTileMesh(tile: XYZTileCoord, segmentsPerSide?: number): XYZTileMesh;
4
+ buildAncestorMesh(targetTile: XYZTileCoord, ancestorTile: XYZTileCoord, segmentsPerSide?: number): XYZTileMesh;
5
+ uploadMesh(mesh: XYZTileMesh, webgl: WebGL2RenderingContext): XYZTileGpuMesh;
6
+ }