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
@@ -38,6 +38,7 @@ export class LatLonGrid extends AbstractSkyEntity {
38
38
  _showGrid = true;
39
39
  _lonArray = [];
40
40
  _latArray = [];
41
+ _bufferKey = '';
41
42
  defaultColor = '#41d4d4';
42
43
  gridText = new GridTextHelper('lonlat');
43
44
  constructor(radius, position, xrad, yrad, name, webgl) {
@@ -84,28 +85,67 @@ export class LatLonGrid extends AbstractSkyEntity {
84
85
  }
85
86
  gl.useProgram(this._shaderProgram);
86
87
  }
87
- initBuffers(fovDeg) {
88
- const steps = xyzFovHelper.getLonLatSteps(fovDeg);
88
+ initBuffers(fovDeg, centerSphericalDeg, coarse = false) {
89
+ const steps = xyzFovHelper.getLonLatSteps(fovDeg, coarse);
89
90
  this._lonStep = steps.lonStep;
90
91
  this._latStep = steps.latStep;
91
92
  this._segmentStep = Math.max(Math.min(this._lonStep, this._latStep), 0.25);
92
93
  this._lonArray = [];
93
94
  this._latArray = [];
94
- for (let lon = -180; lon < 180; lon += this._lonStep) {
95
+ const center = centerSphericalDeg
96
+ ? {
97
+ lon: this.normalizeLon(centerSphericalDeg.phi > 180 ? centerSphericalDeg.phi - 360 : centerSphericalDeg.phi),
98
+ lat: 90 - centerSphericalDeg.theta,
99
+ }
100
+ : null;
101
+ const localGrid = !!center && !coarse && fovDeg < 2;
102
+ const lonValues = localGrid
103
+ ? this.buildLonRange(center.lon, Math.max(fovDeg * 4, this._lonStep * 3), this._lonStep)
104
+ : this.buildLonRange(0, 180, this._lonStep);
105
+ const latValues = localGrid
106
+ ? this.buildLatRange(center.lat, Math.max(fovDeg * 4, this._latStep * 3), this._latStep)
107
+ : this.buildLatRange(0, 90, this._latStep);
108
+ const latSegmentRange = localGrid && center
109
+ ? this.buildLatRange(center.lat, Math.max(fovDeg * 4, this._latStep * 3), this._segmentStep)
110
+ : this.buildLatRange(0, 90, this._segmentStep);
111
+ const lonSegmentRange = localGrid && center
112
+ ? this.buildLonRange(center.lon, Math.max(fovDeg * 4, this._lonStep * 3), this._segmentStep)
113
+ : this.buildLonRange(0, 180, this._segmentStep);
114
+ for (const lon of lonValues) {
95
115
  const vertices = [];
96
- for (let lat = -90; lat <= 90; lat += this._segmentStep) {
116
+ for (const lat of latSegmentRange) {
97
117
  vertices.push(...this.lonLatToCartesian(lon, Math.min(lat, 90)));
98
118
  }
99
119
  this._lonArray.push(new Float32Array(vertices));
100
120
  }
101
- for (let lat = -90 + this._latStep; lat < 90; lat += this._latStep) {
121
+ for (const lat of latValues) {
102
122
  const vertices = [];
103
- for (let lon = -180; lon <= 180; lon += this._segmentStep) {
123
+ if (lat <= -90 || lat >= 90)
124
+ continue;
125
+ for (const lon of lonSegmentRange) {
104
126
  vertices.push(...this.lonLatToCartesian(Math.min(lon, 180), lat));
105
127
  }
106
128
  this._latArray.push(new Float32Array(vertices));
107
129
  }
108
130
  }
131
+ buildLonRange(centerLon, halfSpan, step) {
132
+ const values = [];
133
+ const start = Math.floor((centerLon - halfSpan) / step) * step;
134
+ const end = Math.ceil((centerLon + halfSpan) / step) * step;
135
+ for (let lon = start; lon <= end; lon += step) {
136
+ values.push(this.normalizeLon(lon));
137
+ }
138
+ return values;
139
+ }
140
+ buildLatRange(centerLat, halfSpan, step) {
141
+ const values = [];
142
+ const start = Math.max(-90, Math.floor((centerLat - halfSpan) / step) * step);
143
+ const end = Math.min(90, Math.ceil((centerLat + halfSpan) / step) * step);
144
+ for (let lat = start; lat <= end; lat += step) {
145
+ values.push(lat);
146
+ }
147
+ return values;
148
+ }
109
149
  lonLatToCartesian(lonDeg, latDeg) {
110
150
  const lonRad = degToRad(lonDeg);
111
151
  const latRad = degToRad(latDeg);
@@ -116,17 +156,28 @@ export class LatLonGrid extends AbstractSkyEntity {
116
156
  Math.sin(latRad),
117
157
  ];
118
158
  }
119
- refresh(fovDeg) {
120
- if (Math.abs(this._fovDeg - fovDeg) > 1e-6) {
159
+ refresh(fovDeg, input) {
160
+ const coarse = !!input.cameraMoving;
161
+ const steps = xyzFovHelper.getLonLatSteps(fovDeg, coarse);
162
+ const center = input.centerSphericalDeg;
163
+ const localGrid = !!center && !coarse && fovDeg < 2;
164
+ const centerLon = center ? this.normalizeLon(center.phi > 180 ? center.phi - 360 : center.phi) : 0;
165
+ const centerLat = center ? 90 - center.theta : 0;
166
+ const centerKey = localGrid
167
+ ? `${this.roundToStep(centerLon, Math.max(steps.lonStep, fovDeg))}:${this.roundToStep(centerLat, Math.max(steps.latStep, fovDeg))}`
168
+ : 'global';
169
+ const bufferKey = `${coarse ? 'coarse' : 'settled'}:${steps.lonStep}:${steps.latStep}:${centerKey}`;
170
+ if (this._bufferKey !== bufferKey) {
121
171
  this._fovDeg = fovDeg;
122
- this.initBuffers(this._fovDeg);
172
+ this._bufferKey = bufferKey;
173
+ this.initBuffers(this._fovDeg, input.centerSphericalDeg, coarse);
123
174
  }
124
175
  }
125
176
  refreshFoV(input) {
126
177
  if (!input.camera || !input.pMatrix)
127
178
  return this._fovDeg;
128
179
  this._fovObj.getFoV(global.insideSphere, this, input.camera, input.pMatrix);
129
- this.refresh(this._fovObj.minFoV);
180
+ this.refresh(this._fovObj.minFoV, input);
130
181
  return this._fovObj.minFoV;
131
182
  }
132
183
  getMinFoVDeg() {
@@ -1,6 +1,9 @@
1
1
  declare class FoVHelper {
2
- getHiPSNorder(fov: number): number;
3
- getRADegSteps(fov: number): {
2
+ private static readonly LEVEL_HYSTERESIS;
3
+ private static readonly HIPS_ORDER_MIN_FOV;
4
+ getHiPSNorder(fov: number, currentOrder?: number): number;
5
+ private getRawHiPSNorder;
6
+ getRADegSteps(fov: number, coarse?: boolean): {
4
7
  raStep: number;
5
8
  decStep: number;
6
9
  };
@@ -13,7 +13,40 @@
13
13
  // FoVHelper.ts
14
14
  'use strict';
15
15
  class FoVHelper {
16
- getHiPSNorder(fov) {
16
+ static LEVEL_HYSTERESIS = 0.12;
17
+ static HIPS_ORDER_MIN_FOV = {
18
+ 0: 179,
19
+ 1: 90,
20
+ 2: 30,
21
+ 3: 20,
22
+ 4: 6,
23
+ 5: 3.2,
24
+ 6: 1.6,
25
+ 7: 0.85,
26
+ 8: 0.42,
27
+ 9: 0.21,
28
+ 10: 0.12,
29
+ 11: 0.06,
30
+ 12: 0.015,
31
+ 13: 0,
32
+ };
33
+ getHiPSNorder(fov, currentOrder) {
34
+ const rawOrder = this.getRawHiPSNorder(fov);
35
+ if (currentOrder === undefined || currentOrder === rawOrder)
36
+ return rawOrder;
37
+ if (rawOrder > currentOrder) {
38
+ const boundary = FoVHelper.HIPS_ORDER_MIN_FOV[currentOrder];
39
+ if (boundary > 0 && fov > boundary * (1 - FoVHelper.LEVEL_HYSTERESIS))
40
+ return currentOrder;
41
+ }
42
+ else {
43
+ const boundary = FoVHelper.HIPS_ORDER_MIN_FOV[rawOrder];
44
+ if (boundary > 0 && fov < boundary * (1 + FoVHelper.LEVEL_HYSTERESIS))
45
+ return currentOrder;
46
+ }
47
+ return rawOrder;
48
+ }
49
+ getRawHiPSNorder(fov) {
17
50
  if (fov >= 179)
18
51
  return 0;
19
52
  if (fov >= 90)
@@ -42,10 +75,14 @@ class FoVHelper {
42
75
  return 12;
43
76
  return 13;
44
77
  }
45
- getRADegSteps(fov) {
78
+ getRADegSteps(fov, coarse = false) {
46
79
  let raStep;
47
80
  let decStep;
48
- if (fov >= 179) {
81
+ if (coarse && fov < 0.21) {
82
+ raStep = 10;
83
+ decStep = 10;
84
+ }
85
+ else if (fov >= 179) {
49
86
  raStep = 10;
50
87
  decStep = 10;
51
88
  }
@@ -4,6 +4,7 @@
4
4
  import { AbstractSkyEntity, SkyEntityDrawInput } from '../AbstractSkyEntity.js';
5
5
  import { ColorMap } from '../ColorMaps.js';
6
6
  import { HiPSDescriptor } from './HiPSDescriptor.js';
7
+ import type { HiPSDebugStats } from './HiPSConfig.js';
7
8
  import { HealpixGrid } from '../grid/HealpixGrid.js';
8
9
  export declare class HiPS extends AbstractSkyEntity {
9
10
  private _ancestorTiles;
@@ -41,6 +42,7 @@ export declare class HiPS extends AbstractSkyEntity {
41
42
  changeColorMap(colorMap: ColorMap): void;
42
43
  private initShaders;
43
44
  getCurrentHealpixOrder(): number;
45
+ getDebugStats(): HiPSDebugStats;
44
46
  private refresh;
45
47
  draw(input: SkyEntityDrawInput): void;
46
48
  }
@@ -180,10 +180,31 @@ export class HiPS extends AbstractSkyEntity {
180
180
  getCurrentHealpixOrder() {
181
181
  return this._visibleorder;
182
182
  }
183
- refresh() {
184
- // const fov = healpixGridSingleton.getMinFoV()
185
- const fov = this._healpixGrid.getMinFoV();
186
- this._visibleorder = Math.min(fovHelper.getHiPSNorder(fov), this._maxorder);
183
+ getDebugStats() {
184
+ const tileBuffer = this._healpixGrid.visibleTilesManager.tileBuffer;
185
+ const visibleTiles = this.isGalacticHips
186
+ ? this._healpixGrid.visibleTilesManager.galVisibleTilesByOrder
187
+ : this._healpixGrid.visibleTilesManager.visibleTilesByOrder;
188
+ return {
189
+ activeBaseLayer: 'hips',
190
+ hipsName: this._descriptor.surveyName,
191
+ hipsUrl: this._baseurl,
192
+ isGalactic: this.isGalacticHips,
193
+ currentOrder: visibleTiles.order,
194
+ visibleTileCount: visibleTiles.pixels.length,
195
+ activeTileCount: tileBuffer.activeTileCount,
196
+ cachedTileCount: tileBuffer.cachedTileCount,
197
+ cacheSize: tileBuffer.size,
198
+ readyTileCount: tileBuffer.readyTileCount,
199
+ loadingTileCount: tileBuffer.loadingTileCount,
200
+ };
201
+ }
202
+ refresh(input) {
203
+ // const fov = this._healpixGrid.getMinFoV()
204
+ // this._visibleorder = Math.min(fovHelper.getHiPSNorder(fov), this._maxorder)
205
+ const rawFov = input.fovDeg ?? this._healpixGrid.getMinFoV();
206
+ const fov = Number.isFinite(rawFov) && rawFov > 0 ? rawFov : 1e-6;
207
+ this._visibleorder = Math.min(fovHelper.getHiPSNorder(fov, this._visibleorder), this._maxorder);
187
208
  }
188
209
  draw(input) {
189
210
  const vMatrix = input.camera.getCameraMatrix();
@@ -192,7 +213,7 @@ export class HiPS extends AbstractSkyEntity {
192
213
  const pMatrix = input.pMatrix;
193
214
  if (!pMatrix)
194
215
  return;
195
- this.refresh();
216
+ this.refresh(input);
196
217
  const mMatrix = this.getModelMatrix();
197
218
  super.hipsShaderProgram.setRuntimeColorMap(this.colorMap);
198
219
  if (this._allSky && this._allSkyTile) {
@@ -0,0 +1,13 @@
1
+ export type HiPSDebugStats = {
2
+ activeBaseLayer: 'hips' | 'xyz' | null;
3
+ hipsName: string | null;
4
+ hipsUrl: string | null;
5
+ isGalactic: boolean | null;
6
+ currentOrder: number | null;
7
+ visibleTileCount: number;
8
+ activeTileCount: number;
9
+ cachedTileCount: number;
10
+ cacheSize: number;
11
+ readyTileCount: number;
12
+ loadingTileCount: number;
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -32,6 +32,7 @@ export default class Tile {
32
32
  constructor(tileno: number, order: number, hips: HiPS, tileBuffer: TileBuffer, webgl: WebGL2RenderingContext, visibleTileManager: VisibleTilesManager);
33
33
  destroyIntervals(): void;
34
34
  getReadyState(): boolean;
35
+ isLoading(): boolean;
35
36
  get cacheTime0(): number | undefined;
36
37
  resetCacheTime0(): void;
37
38
  setCacheTime0(): void;
@@ -65,6 +65,9 @@ export default class Tile {
65
65
  getReadyState() {
66
66
  return this._ready;
67
67
  }
68
+ isLoading() {
69
+ return !this._ready && !this._abort;
70
+ }
68
71
  get cacheTime0() {
69
72
  return this._cacheTime0;
70
73
  }
@@ -35,4 +35,9 @@ export declare class TileBuffer {
35
35
  private key;
36
36
  /** Optional: call to stop internal timers if you dispose this buffer. */
37
37
  dispose(): void;
38
+ get size(): number;
39
+ get activeTileCount(): number;
40
+ get cachedTileCount(): number;
41
+ get readyTileCount(): number;
42
+ get loadingTileCount(): number;
38
43
  }
@@ -154,6 +154,55 @@ export class TileBuffer {
154
154
  dispose() {
155
155
  window.clearInterval(this._cleanerId);
156
156
  }
157
+ get size() {
158
+ return this._tiles.size + this._cachedTiles.size + this._galTiles.size + this._galCachedTiles.size;
159
+ }
160
+ get activeTileCount() {
161
+ return this._tiles.size + this._galTiles.size;
162
+ }
163
+ get cachedTileCount() {
164
+ return this._cachedTiles.size + this._galCachedTiles.size;
165
+ }
166
+ get readyTileCount() {
167
+ let count = 0;
168
+ for (const tile of this._tiles.values()) {
169
+ if (tile.getReadyState())
170
+ count++;
171
+ }
172
+ for (const tile of this._galTiles.values()) {
173
+ if (tile.getReadyState())
174
+ count++;
175
+ }
176
+ for (const tile of this._cachedTiles.values()) {
177
+ if (tile.getReadyState())
178
+ count++;
179
+ }
180
+ for (const tile of this._galCachedTiles.values()) {
181
+ if (tile.getReadyState())
182
+ count++;
183
+ }
184
+ return count;
185
+ }
186
+ get loadingTileCount() {
187
+ let count = 0;
188
+ for (const tile of this._tiles.values()) {
189
+ if (tile.isLoading())
190
+ count++;
191
+ }
192
+ for (const tile of this._galTiles.values()) {
193
+ if (tile.isLoading())
194
+ count++;
195
+ }
196
+ for (const tile of this._cachedTiles.values()) {
197
+ if (tile.isLoading())
198
+ count++;
199
+ }
200
+ for (const tile of this._galCachedTiles.values()) {
201
+ if (tile.isLoading())
202
+ count++;
203
+ }
204
+ return count;
205
+ }
157
206
  }
158
207
  // Singleton (kept for compatibility with your original export)
159
208
  // export const newTileBuffer = new TileBuffer()
@@ -1,4 +1,10 @@
1
1
  import { FootprintSetGL } from '../footprints/FootprintSetGL.js';
2
+ import { CoordsType } from '../../utils/CoordsType.js';
3
+ import { ParsedGeoJSONFeature } from '../../utils/GeoJSONParser.js';
2
4
  export declare class TerraFootprintSetGL extends FootprintSetGL {
3
5
  _kind: string;
6
+ protected _coordsType: CoordsType.GEOGRAPHIC;
7
+ addGeoJSONFeatures(features: ParsedGeoJSONFeature[]): void;
8
+ private createGeoJSONMetadataColumns;
9
+ private createGeoJSONDetails;
4
10
  }
@@ -11,6 +11,48 @@
11
11
  * See LICENSE.md, LICENSE-COMMERCIAL.md, and LICENSE-NONCOMMERCIAL.md for details.
12
12
  */
13
13
  import { FootprintSetGL } from '../footprints/FootprintSetGL.js';
14
+ import { CoordsType } from '../../utils/CoordsType.js';
15
+ import { Footprint } from '../footprints/Footprint.js';
16
+ import { MetadataColumn } from '../MetadataColumn.js';
17
+ import { MetadataManager } from '../MetadataManager.js';
18
+ import { ColumnType } from '../MetadataColumn.js';
14
19
  export class TerraFootprintSetGL extends FootprintSetGL {
15
20
  _kind = 'TerraFootprintSetGL';
21
+ _coordsType = CoordsType.GEOGRAPHIC;
22
+ addGeoJSONFeatures(features) {
23
+ this._ready = false;
24
+ this.clearFootprints();
25
+ this._metadataManager = new MetadataManager(this.createGeoJSONMetadataColumns(features));
26
+ for (const feature of features) {
27
+ const footprint = Footprint.fromPolygons(feature.polygons, this.createGeoJSONDetails(feature), CoordsType.GEOGRAPHIC);
28
+ if (footprint.valid) {
29
+ this.addFootprint(footprint);
30
+ this.totPoints += footprint.totPoints;
31
+ this.totConvexPoints += footprint.totConvexPoints;
32
+ }
33
+ }
34
+ this._ready = true;
35
+ this._bufferInitialised = false;
36
+ }
37
+ createGeoJSONMetadataColumns(features) {
38
+ const names = new Set();
39
+ features.forEach(feature => Object.keys(feature.properties).forEach(name => names.add(name)));
40
+ return Array.from(names).map((name, index) => {
41
+ const values = features.map(feature => feature.properties[name]).filter(value => value !== null && value !== undefined && value !== '');
42
+ const isNumber = values.length > 0 && values.every(value => typeof value === 'number' || !Number.isNaN(Number(value)));
43
+ const isName = /^name$|nome|denominazione|label|title/i.test(name);
44
+ return new MetadataColumn({
45
+ index,
46
+ name,
47
+ columnType: isName ? ColumnType.MAIN_NAME : (isNumber ? ColumnType.NUMBER : ColumnType.STRING),
48
+ unit: '',
49
+ });
50
+ });
51
+ }
52
+ createGeoJSONDetails(feature) {
53
+ return Object.entries(feature.properties).map(([key, value]) => ({
54
+ key,
55
+ value: typeof value === 'number' ? value : String(value ?? ''),
56
+ }));
57
+ }
16
58
  }
@@ -5,5 +5,6 @@
5
5
  export declare enum CoordsType {
6
6
  CARTESIAN = "cartesian",
7
7
  SPHERICAL = "spherical",
8
- ASTRO = "astro"
8
+ ASTRO = "astro",
9
+ GEOGRAPHIC = "geographic"
9
10
  }
@@ -19,5 +19,6 @@ export var CoordsType;
19
19
  CoordsType["CARTESIAN"] = "cartesian";
20
20
  CoordsType["SPHERICAL"] = "spherical";
21
21
  CoordsType["ASTRO"] = "astro";
22
+ CoordsType["GEOGRAPHIC"] = "geographic";
22
23
  })(CoordsType || (CoordsType = {}));
23
24
  // export default CoordsType;
@@ -0,0 +1,19 @@
1
+ import { Point } from '../model/Point.js';
2
+ export type GeoJSONProperties = Record<string, unknown>;
3
+ export interface ParsedGeoJSONFeature {
4
+ id?: string | number;
5
+ geometryType: 'Polygon' | 'MultiPolygon';
6
+ properties: GeoJSONProperties;
7
+ polygons: Point[][];
8
+ }
9
+ declare class GeoJSONParser {
10
+ static isGeoJSON(value: unknown): boolean;
11
+ static parseGeoJSON(value: unknown): ParsedGeoJSONFeature[];
12
+ private static parseFeature;
13
+ private static parseGeometry;
14
+ private static parseMultiPolygonCoordinates;
15
+ private static parsePolygonCoordinates;
16
+ private static parseLinearRing;
17
+ private static parsePosition;
18
+ }
19
+ export default GeoJSONParser;
@@ -0,0 +1,112 @@
1
+ /*
2
+ * AstroViewer
3
+ * Copyright (C) Fabrizio Giordano
4
+ * SPDX-License-Identifier: LicenseRef-AstroViewer-Dual-License
5
+ *
6
+ * This file is part of AstroViewer.
7
+ * AstroViewer is distributed under a dual-license model.
8
+ * Commercial use requires a separate commercial license.
9
+ * Non-commercial use is governed by LICENSE-NONCOMMERCIAL.md.
10
+ *
11
+ * See LICENSE.md, LICENSE-COMMERCIAL.md, and LICENSE-NONCOMMERCIAL.md for details.
12
+ */
13
+ import { CoordsType } from './CoordsType.js';
14
+ import { Point } from '../model/Point.js';
15
+ class GeoJSONParser {
16
+ static isGeoJSON(value) {
17
+ if (!value || typeof value !== 'object')
18
+ return false;
19
+ const type = value.type;
20
+ return type === 'FeatureCollection'
21
+ || type === 'Feature'
22
+ || type === 'Polygon'
23
+ || type === 'MultiPolygon'
24
+ || type === 'GeometryCollection';
25
+ }
26
+ static parseGeoJSON(value) {
27
+ if (!value || typeof value !== 'object') {
28
+ throw new Error('GeoJSON root must be an object');
29
+ }
30
+ const obj = value;
31
+ if (obj.type === 'FeatureCollection') {
32
+ if (!Array.isArray(obj.features))
33
+ throw new Error('GeoJSON FeatureCollection has no features array');
34
+ return obj.features.flatMap((feature) => GeoJSONParser.parseFeature(feature));
35
+ }
36
+ if (obj.type === 'Feature')
37
+ return GeoJSONParser.parseFeature(obj);
38
+ if (obj.type === 'Polygon' || obj.type === 'MultiPolygon' || obj.type === 'GeometryCollection') {
39
+ return GeoJSONParser.parseGeometry(obj, {});
40
+ }
41
+ throw new Error(`Unsupported GeoJSON type: ${obj.type ?? 'unknown'}`);
42
+ }
43
+ static parseFeature(value) {
44
+ if (!value || typeof value !== 'object')
45
+ throw new Error('GeoJSON feature must be an object');
46
+ const feature = value;
47
+ if (feature.type !== 'Feature')
48
+ throw new Error('GeoJSON feature has invalid type');
49
+ if (!feature.geometry)
50
+ return [];
51
+ return GeoJSONParser.parseGeometry(feature.geometry, feature.properties ?? {}, feature.id);
52
+ }
53
+ static parseGeometry(geometry, properties, id) {
54
+ if (geometry.type === 'Polygon') {
55
+ return [{
56
+ id,
57
+ geometryType: 'Polygon',
58
+ properties,
59
+ polygons: GeoJSONParser.parsePolygonCoordinates(geometry.coordinates),
60
+ }];
61
+ }
62
+ if (geometry.type === 'MultiPolygon') {
63
+ return [{
64
+ id,
65
+ geometryType: 'MultiPolygon',
66
+ properties,
67
+ polygons: GeoJSONParser.parseMultiPolygonCoordinates(geometry.coordinates),
68
+ }];
69
+ }
70
+ if (geometry.type === 'GeometryCollection') {
71
+ if (!Array.isArray(geometry.geometries))
72
+ return [];
73
+ return geometry.geometries.flatMap((child) => GeoJSONParser.parseGeometry(child, properties, id));
74
+ }
75
+ return [];
76
+ }
77
+ static parseMultiPolygonCoordinates(coordinates) {
78
+ if (!Array.isArray(coordinates))
79
+ throw new Error('GeoJSON MultiPolygon coordinates must be an array');
80
+ return coordinates.flatMap((polygonCoordinates) => GeoJSONParser.parsePolygonCoordinates(polygonCoordinates));
81
+ }
82
+ static parsePolygonCoordinates(coordinates) {
83
+ if (!Array.isArray(coordinates))
84
+ throw new Error('GeoJSON Polygon coordinates must be an array');
85
+ return coordinates
86
+ .map((ring) => GeoJSONParser.parseLinearRing(ring))
87
+ .filter((ring) => ring.length >= 3);
88
+ }
89
+ static parseLinearRing(ring) {
90
+ if (!Array.isArray(ring))
91
+ throw new Error('GeoJSON linear ring must be an array');
92
+ const points = ring.map((position) => GeoJSONParser.parsePosition(position));
93
+ if (points.length > 1) {
94
+ const first = points[0];
95
+ const last = points[points.length - 1];
96
+ if (first.lonDeg === last.lonDeg && first.latDeg === last.latDeg)
97
+ points.pop();
98
+ }
99
+ return points;
100
+ }
101
+ static parsePosition(position) {
102
+ if (!Array.isArray(position) || position.length < 2) {
103
+ throw new Error('GeoJSON position must be [longitude, latitude]');
104
+ }
105
+ const [lonDeg, latDeg] = position;
106
+ if (!Number.isFinite(lonDeg) || !Number.isFinite(latDeg)) {
107
+ throw new Error('GeoJSON position contains non-finite longitude/latitude');
108
+ }
109
+ return new Point({ lonDeg, latDeg }, CoordsType.GEOGRAPHIC);
110
+ }
111
+ }
112
+ export default GeoJSONParser;
@@ -11,6 +11,7 @@
11
11
  * See LICENSE.md, LICENSE-COMMERCIAL.md, and LICENSE-NONCOMMERCIAL.md for details.
12
12
  */
13
13
  import { mat4 } from "gl-matrix";
14
+ import { bootSetup } from "../Config.js";
14
15
  export class PerspectiveMatrixManager {
15
16
  _pMatrix;
16
17
  _aspectRatio = 1;
@@ -41,7 +42,9 @@ export class PerspectiveMatrixManager {
41
42
  const cf = c2 * Math.sin(beta);
42
43
  farPlane = cf > 0 ? cf : r;
43
44
  }
44
- mat4.perspective(p, (fovDeg * Math.PI) / 180, this._aspectRatio, nearPlane, farPlane);
45
+ const effectiveFovDeg = insideSphere ? bootSetup.inside_camera_fov_deg : fovDeg;
46
+ const effectiveNearPlane = insideSphere ? Math.max(nearPlane, 0.001) : nearPlane;
47
+ mat4.perspective(p, (effectiveFovDeg * Math.PI) / 180, this._aspectRatio, effectiveNearPlane, farPlane);
45
48
  this._pMatrix = p;
46
49
  return p;
47
50
  }
@@ -2,14 +2,18 @@
2
2
  * @author Fabrizio Giordano (Fab77)
3
3
  */
4
4
  import { Point } from "../model/Point.js";
5
+ import { CoordsType } from "./CoordsType.js";
5
6
  export interface STCSParseResult {
6
7
  totpoints: number;
7
8
  polygons: Point[][];
8
9
  }
10
+ export interface STCSParseOptions {
11
+ coordsType?: CoordsType.ASTRO | CoordsType.GEOGRAPHIC;
12
+ }
9
13
  declare class STCSParser {
10
- static parseSTCS(stcs: string): STCSParseResult;
14
+ static parseSTCS(stcs: string, options?: STCSParseOptions): STCSParseResult;
11
15
  static cleanStcs(stcs: string): string;
12
- static parsePolygon(stcs: string): STCSParseResult;
13
- static parseCircle(stcs: string): STCSParseResult;
16
+ static parsePolygon(stcs: string, options?: STCSParseOptions): STCSParseResult;
17
+ static parseCircle(stcs: string, options?: STCSParseOptions): STCSParseResult;
14
18
  }
15
19
  export default STCSParser;