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.
- package/dist/astroviewer.cjs +706 -204
- package/dist/astroviewer.js +1 -1
- package/dist/astroviewer.min.js +1 -1
- package/lib-esm/AstroSphere.d.ts +19 -15
- package/lib-esm/AstroSphere.js +163 -94
- package/lib-esm/AstroViewer.d.ts +2 -0
- package/lib-esm/AstroViewer.js +3 -0
- package/lib-esm/Camera.js +4 -6
- package/lib-esm/Config.d.ts +2 -0
- package/lib-esm/Config.js +3 -1
- package/lib-esm/index.d.ts +2 -0
- package/lib-esm/index.js +1 -0
- package/lib-esm/model/AbstractSkyEntity.d.ts +1 -0
- package/lib-esm/model/MetadataManager.d.ts +1 -0
- package/lib-esm/model/MetadataManager.js +4 -1
- package/lib-esm/model/Point.d.ts +9 -1
- package/lib-esm/model/Point.js +18 -0
- package/lib-esm/model/SphereFoV.js +4 -3
- package/lib-esm/model/earth/XYZConfig.d.ts +4 -0
- package/lib-esm/model/earth/XYZFoVHelper.d.ts +5 -2
- package/lib-esm/model/earth/XYZFoVHelper.js +40 -3
- package/lib-esm/model/footprints/Footprint.d.ts +4 -1
- package/lib-esm/model/footprints/Footprint.js +18 -2
- package/lib-esm/model/footprints/FootprintSetGL.d.ts +4 -2
- package/lib-esm/model/footprints/FootprintSetGL.js +9 -6
- package/lib-esm/model/grid/EquatorialGrid.d.ts +2 -1
- package/lib-esm/model/grid/EquatorialGrid.js +10 -6
- package/lib-esm/model/grid/HealpixGrid.js +5 -2
- package/lib-esm/model/grid/LonLatGrid.d.ts +3 -0
- package/lib-esm/model/grid/LonLatGrid.js +61 -10
- package/lib-esm/model/hips/FoVHelper.d.ts +5 -2
- package/lib-esm/model/hips/FoVHelper.js +40 -3
- package/lib-esm/model/hips/HiPS.d.ts +2 -0
- package/lib-esm/model/hips/HiPS.js +26 -5
- package/lib-esm/model/hips/HiPSConfig.d.ts +13 -0
- package/lib-esm/model/hips/HiPSConfig.js +1 -0
- package/lib-esm/model/hips/Tile.d.ts +1 -0
- package/lib-esm/model/hips/Tile.js +3 -0
- package/lib-esm/model/hips/TileBuffer.d.ts +5 -0
- package/lib-esm/model/hips/TileBuffer.js +49 -0
- package/lib-esm/model/terra/TerraFootprintSetGL.d.ts +6 -0
- package/lib-esm/model/terra/TerraFootprintSetGL.js +42 -0
- package/lib-esm/utils/CoordsType.d.ts +2 -1
- package/lib-esm/utils/CoordsType.js +1 -0
- package/lib-esm/utils/GeoJSONParser.d.ts +19 -0
- package/lib-esm/utils/GeoJSONParser.js +112 -0
- package/lib-esm/utils/PerspectiveMatrixManager.js +4 -1
- package/lib-esm/utils/STCSParser.d.ts +7 -3
- package/lib-esm/utils/STCSParser.js +15 -9
- 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
|
-
|
|
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 (
|
|
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 (
|
|
121
|
+
for (const lat of latValues) {
|
|
102
122
|
const vertices = [];
|
|
103
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
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;
|
|
@@ -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
|
}
|
|
@@ -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
|
-
|
|
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;
|