astro-viewer 3.10.0 → 3.11.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/README.md +66 -4
- package/dist/astroviewer.cjs +353 -8
- package/dist/astroviewer.cjs.map +1 -1
- package/dist/astroviewer.js +353 -8
- package/dist/astroviewer.js.map +1 -1
- package/dist/astroviewer.min.js +1 -1
- package/dist/astroviewer.min.js.map +1 -1
- package/lib-esm/AstroSphere.d.ts +1 -0
- package/lib-esm/AstroSphere.d.ts.map +1 -1
- package/lib-esm/AstroSphere.js +15 -0
- package/lib-esm/AstroSphere.js.map +1 -1
- package/lib-esm/AstroViewer.d.ts +1 -0
- package/lib-esm/AstroViewer.d.ts.map +1 -1
- package/lib-esm/AstroViewer.js +3 -0
- package/lib-esm/AstroViewer.js.map +1 -1
- package/lib-esm/Camera.d.ts +6 -0
- package/lib-esm/Camera.d.ts.map +1 -1
- package/lib-esm/Camera.js +102 -8
- package/lib-esm/Camera.js.map +1 -1
- package/lib-esm/model/Source.d.ts +11 -0
- package/lib-esm/model/Source.d.ts.map +1 -1
- package/lib-esm/model/Source.js +7 -0
- package/lib-esm/model/Source.js.map +1 -1
- package/lib-esm/model/catalogues/CatalogueGL.d.ts +23 -1
- package/lib-esm/model/catalogues/CatalogueGL.d.ts.map +1 -1
- package/lib-esm/model/catalogues/CatalogueGL.js +223 -1
- package/lib-esm/model/catalogues/CatalogueGL.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,8 +21,12 @@ AstroViewer is developed from scratch without depending on visualization framewo
|
|
|
21
21
|
- HiPS coverage handling
|
|
22
22
|
- HEALPix hierarchical sky tessellation
|
|
23
23
|
- FITS astronomical data support
|
|
24
|
+
- FITS display scaling (linear, sqrt, log, asinh, gamma)
|
|
25
|
+
- FITS display range modes
|
|
26
|
+
- Runtime color maps
|
|
24
27
|
- Astronomical coordinate systems and transformations
|
|
25
28
|
- Field-of-view calculations
|
|
29
|
+
- Interactive navigation and animated Fly To
|
|
26
30
|
- Grid and coordinate visualization
|
|
27
31
|
- TAP-based astronomical data access
|
|
28
32
|
|
|
@@ -34,6 +38,7 @@ AstroViewer is developed from scratch without depending on visualization framewo
|
|
|
34
38
|
- GeoJSON geometries and overlays
|
|
35
39
|
- Satellite tracks and observation geometry
|
|
36
40
|
- Sensor footprints and cones
|
|
41
|
+
- Longitude/latitude navigation and animated Fly To
|
|
37
42
|
- Earth-oriented coordinate and picking utilities
|
|
38
43
|
|
|
39
44
|
### 3D and Scientific Visualization
|
|
@@ -42,7 +47,7 @@ AstroViewer is developed from scratch without depending on visualization framewo
|
|
|
42
47
|
- Inside/outside sphere visualization
|
|
43
48
|
- MeshHiPS OBJ tiled meshes
|
|
44
49
|
- Runtime color maps
|
|
45
|
-
- Interactive camera and
|
|
50
|
+
- Interactive camera, navigation and animated Fly To
|
|
46
51
|
- Ray picking
|
|
47
52
|
- Scientific overlays and geometries
|
|
48
53
|
- Framework-independent architecture
|
|
@@ -100,6 +105,7 @@ A HiPS survey can be activated using a `HiPSDescriptor`:
|
|
|
100
105
|
import { AstroViewer, HiPSDescriptor } from "astro-viewer";
|
|
101
106
|
|
|
102
107
|
const canvas = document.getElementById("astrocanvas");
|
|
108
|
+
|
|
103
109
|
const viewer = new AstroViewer(canvas);
|
|
104
110
|
|
|
105
111
|
const hipsUrl = "https://alasky.cds.unistra.fr/DSS/DSSColor/";
|
|
@@ -189,6 +195,26 @@ AstroViewer supports conventional image-based HiPS tiles and FITS HiPS tiles.
|
|
|
189
195
|
|
|
190
196
|
---
|
|
191
197
|
|
|
198
|
+
## FITS HiPS Visualization
|
|
199
|
+
|
|
200
|
+
AstroViewer can render FITS tiles directly as part of the HiPS rendering pipeline.
|
|
201
|
+
|
|
202
|
+
FITS visualization supports multiple display scaling functions:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
linear
|
|
206
|
+
sqrt
|
|
207
|
+
log
|
|
208
|
+
asinh
|
|
209
|
+
gamma
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The display range and scaling parameters can be changed at runtime, allowing scientific image data with different dynamic ranges to be explored interactively.
|
|
213
|
+
|
|
214
|
+
Color maps can also be applied at runtime to FITS-based HiPS data.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
192
218
|
## HiPS Coverage
|
|
193
219
|
|
|
194
220
|
AstroViewer supports survey coverage information associated with HiPS datasets.
|
|
@@ -199,6 +225,38 @@ This is particularly useful for partial-sky surveys.
|
|
|
199
225
|
|
|
200
226
|
---
|
|
201
227
|
|
|
228
|
+
## Navigation and Fly To
|
|
229
|
+
|
|
230
|
+
AstroViewer provides immediate and animated camera navigation.
|
|
231
|
+
|
|
232
|
+
The viewer can move directly to a target coordinate:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
viewer.goTo(firstDeg, secondDeg);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
or smoothly animate the camera towards the target:
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
viewer.flyTo(firstDeg, secondDeg);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
An optional duration can be specified in milliseconds:
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
viewer.flyTo(firstDeg, secondDeg, 1500);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The coordinate semantics depend on the active visualization domain:
|
|
251
|
+
|
|
252
|
+
- Astronomy uses right ascension and declination.
|
|
253
|
+
- Earth visualization uses longitude and latitude.
|
|
254
|
+
- Planetary and MeshHiPS visualization uses longitude and latitude.
|
|
255
|
+
|
|
256
|
+
Fly To uses spherical interpolation and integrates with the viewer rendering loop. User interaction interrupts an active Fly To operation, allowing normal interactive navigation to resume immediately.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
202
260
|
## Earth Maps
|
|
203
261
|
|
|
204
262
|
AstroViewer also supports tiled Earth observation maps.
|
|
@@ -265,17 +323,21 @@ addHiPS(...)
|
|
|
265
323
|
addHiPSFromUrl(...)
|
|
266
324
|
removeHiPS(...)
|
|
267
325
|
removeAllHiPS()
|
|
268
|
-
|
|
269
326
|
getActiveHiPS()
|
|
270
327
|
getActiveHiPSLayers()
|
|
271
328
|
setActiveHiPS(...)
|
|
272
|
-
|
|
273
329
|
getActiveHiPSFormats()
|
|
274
330
|
changeHiPSFormat(...)
|
|
275
|
-
|
|
276
331
|
setHiPSOpacity(...)
|
|
277
332
|
```
|
|
278
333
|
|
|
334
|
+
### Navigation
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
goTo(...)
|
|
338
|
+
flyTo(...)
|
|
339
|
+
```
|
|
340
|
+
|
|
279
341
|
### Maps and visualization
|
|
280
342
|
|
|
281
343
|
AstroViewer also exposes functionality around:
|
package/dist/astroviewer.cjs
CHANGED
|
@@ -43,6 +43,7 @@ __webpack_require__.d(vec3_namespaceObject, {
|
|
|
43
43
|
dot: () => (dot),
|
|
44
44
|
fromValues: () => (vec3_fromValues),
|
|
45
45
|
length: () => (vec3_length),
|
|
46
|
+
lerp: () => (lerp),
|
|
46
47
|
normalize: () => (normalize),
|
|
47
48
|
scale: () => (vec3_scale),
|
|
48
49
|
scaleAndAdd: () => (scaleAndAdd),
|
|
@@ -3895,6 +3896,7 @@ class AstroSphere {
|
|
|
3895
3896
|
const handleMouseDown = (event) => {
|
|
3896
3897
|
canvas.setPointerCapture(event.pointerId);
|
|
3897
3898
|
this.mouseDown = true;
|
|
3899
|
+
this._camera.cancelFlyTo();
|
|
3898
3900
|
const rect = canvas.getBoundingClientRect();
|
|
3899
3901
|
this.lastMouseX = event.clientX - rect.left; // locale al canvas
|
|
3900
3902
|
this.lastMouseY = event.clientY - rect.top; // locale al canvas
|
|
@@ -4021,6 +4023,7 @@ class AstroSphere {
|
|
|
4021
4023
|
const handleMouseWheel = (event) => {
|
|
4022
4024
|
const currentFov = this._healpixGrid.getMinFoV();
|
|
4023
4025
|
const zoomStep = this.computeZoomStep(currentFov, event.deltaY);
|
|
4026
|
+
this._camera.cancelFlyTo();
|
|
4024
4027
|
// Apply wheel zoom immediately and discard any queued inertia so reversing
|
|
4025
4028
|
// direction feels responsive instead of "buffered".
|
|
4026
4029
|
this.zoomInertia = 0;
|
|
@@ -4286,6 +4289,9 @@ class AstroSphere {
|
|
|
4286
4289
|
goTo(raDeg, decDeg) {
|
|
4287
4290
|
this._camera.goTo(raDeg, decDeg);
|
|
4288
4291
|
}
|
|
4292
|
+
flyTo(raDeg, decDeg, durationMs = 1200) {
|
|
4293
|
+
this._camera.flyTo(raDeg, decDeg, durationMs);
|
|
4294
|
+
}
|
|
4289
4295
|
getActiveCoordinateMode() {
|
|
4290
4296
|
if (this._activeBaseLayer === "xyz") {
|
|
4291
4297
|
return "lonlat";
|
|
@@ -4521,6 +4527,16 @@ class AstroSphere {
|
|
|
4521
4527
|
let PHI = 0;
|
|
4522
4528
|
this._webgl.viewport(0, 0, this._webgl.drawingBufferWidth, this._webgl.drawingBufferHeight);
|
|
4523
4529
|
this._webgl.clear(this._webgl.COLOR_BUFFER_BIT | this._webgl.DEPTH_BUFFER_BIT);
|
|
4530
|
+
const now = performance.now();
|
|
4531
|
+
const cameraFlying = this._camera.updateFlyTo(now);
|
|
4532
|
+
if (cameraFlying) {
|
|
4533
|
+
this.inertiaX = 0;
|
|
4534
|
+
this.inertiaY = 0;
|
|
4535
|
+
this.lastCameraMotionAt = now;
|
|
4536
|
+
this._cameraStatusChanged = true;
|
|
4537
|
+
this.updateCentralPoint();
|
|
4538
|
+
this._perspectiveMatrixManager.computePerspectiveMatrix(canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, Global_js_1.default.insideSphere);
|
|
4539
|
+
}
|
|
4524
4540
|
// Zoom inertia
|
|
4525
4541
|
if (this.zoomInertia !== 0) {
|
|
4526
4542
|
if (Math.abs(this.zoomInertia) > 0.0001) {
|
|
@@ -5051,6 +5067,9 @@ class AstroViewer {
|
|
|
5051
5067
|
// console.log(`AstroViewer.goTo goto(${raDeg}, ${decDeg})`)
|
|
5052
5068
|
this.astroSphere.goTo(raDeg, decDeg);
|
|
5053
5069
|
}
|
|
5070
|
+
flyTo(raDeg, decDeg, durationMs = 1200) {
|
|
5071
|
+
this.astroSphere.flyTo(raDeg, decDeg, durationMs);
|
|
5072
|
+
}
|
|
5054
5073
|
getActiveCoordinateMode() {
|
|
5055
5074
|
return this.astroSphere.getActiveCoordinateMode();
|
|
5056
5075
|
}
|
|
@@ -5372,6 +5391,7 @@ class Camera {
|
|
|
5372
5391
|
lockRotX = false;
|
|
5373
5392
|
lockRotY = false;
|
|
5374
5393
|
lockRotZ = false;
|
|
5394
|
+
flyToAnimation = null;
|
|
5375
5395
|
constructor(in_position, in_sphere) {
|
|
5376
5396
|
this.init(in_position, in_sphere);
|
|
5377
5397
|
}
|
|
@@ -5381,7 +5401,11 @@ class Camera {
|
|
|
5381
5401
|
this.vMatrix = gl_matrix_1.mat4.create();
|
|
5382
5402
|
this.T = gl_matrix_1.mat4.create();
|
|
5383
5403
|
this.R = gl_matrix_1.mat4.create();
|
|
5384
|
-
gl_matrix_1.mat4.translate(this.T, this.T, [
|
|
5404
|
+
gl_matrix_1.mat4.translate(this.T, this.T, [
|
|
5405
|
+
this.cam_pos[0],
|
|
5406
|
+
this.cam_pos[1],
|
|
5407
|
+
this.cam_pos[2],
|
|
5408
|
+
]);
|
|
5385
5409
|
// reset helpers
|
|
5386
5410
|
this.FoV = this.previousFoV = 180.0;
|
|
5387
5411
|
this.move = gl_matrix_1.vec3.clone([0, 0, 0]);
|
|
@@ -5390,11 +5414,15 @@ class Camera {
|
|
|
5390
5414
|
this.goTo(raDeg, decDeg);
|
|
5391
5415
|
}
|
|
5392
5416
|
goTo(raDeg, decDeg) {
|
|
5417
|
+
this.cancelFlyTo();
|
|
5393
5418
|
this.goToPhiTheta((0, Utils_js_1.astroDegToSpherical)(raDeg, decDeg));
|
|
5394
5419
|
}
|
|
5395
5420
|
goToPhiTheta(ptDeg) {
|
|
5396
5421
|
const xyz = (0, Utils_js_1.sphericalToCartesian)(ptDeg.phi, ptDeg.theta, this.cam_pos[2]);
|
|
5397
|
-
|
|
5422
|
+
this.applyCameraPosition(gl_matrix_1.vec3.fromValues(xyz[0], xyz[1], xyz[2]));
|
|
5423
|
+
}
|
|
5424
|
+
applyCameraPosition(cameraPosition) {
|
|
5425
|
+
const targetDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), cameraPosition);
|
|
5398
5426
|
const celestialNorth = gl_matrix_1.vec3.fromValues(0.0, 0.0, 1.0);
|
|
5399
5427
|
const northProjection = gl_matrix_1.vec3.scale(gl_matrix_1.vec3.create(), targetDirection, gl_matrix_1.vec3.dot(celestialNorth, targetDirection));
|
|
5400
5428
|
const cameraUp = gl_matrix_1.vec3.subtract(gl_matrix_1.vec3.create(), celestialNorth, northProjection);
|
|
@@ -5405,9 +5433,13 @@ class Camera {
|
|
|
5405
5433
|
gl_matrix_1.vec3.normalize(cameraUp, cameraUp);
|
|
5406
5434
|
}
|
|
5407
5435
|
let cameraMatrix = gl_matrix_1.mat4.create();
|
|
5408
|
-
cameraMatrix = gl_matrix_1.mat4.translate(cameraMatrix, cameraMatrix,
|
|
5436
|
+
cameraMatrix = gl_matrix_1.mat4.translate(cameraMatrix, cameraMatrix, cameraPosition);
|
|
5409
5437
|
const focusPoint = [0.0, 0.0, 0.0];
|
|
5410
|
-
const cameraPos = [
|
|
5438
|
+
const cameraPos = [
|
|
5439
|
+
cameraMatrix[12],
|
|
5440
|
+
cameraMatrix[13],
|
|
5441
|
+
cameraMatrix[14],
|
|
5442
|
+
];
|
|
5411
5443
|
cameraMatrix = gl_matrix_1.mat4.targetTo(cameraMatrix, cameraPos, focusPoint, cameraUp);
|
|
5412
5444
|
this.R = gl_matrix_1.mat4.clone(cameraMatrix);
|
|
5413
5445
|
this.R[12] = 0;
|
|
@@ -5419,6 +5451,81 @@ class Camera {
|
|
|
5419
5451
|
}
|
|
5420
5452
|
this.vMatrix = viewMatrix;
|
|
5421
5453
|
}
|
|
5454
|
+
flyTo(raDeg, decDeg, durationMs = 1200) {
|
|
5455
|
+
const targetSpherical = (0, Utils_js_1.astroDegToSpherical)(raDeg, decDeg);
|
|
5456
|
+
const targetPositionValues = (0, Utils_js_1.sphericalToCartesian)(targetSpherical.phi, targetSpherical.theta, this.cam_pos[2]);
|
|
5457
|
+
const targetPosition = gl_matrix_1.vec3.fromValues(targetPositionValues[0], targetPositionValues[1], targetPositionValues[2]);
|
|
5458
|
+
const currentPosition = this.getCameraPosition();
|
|
5459
|
+
const startDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), gl_matrix_1.vec3.fromValues(currentPosition[0], currentPosition[1], currentPosition[2]));
|
|
5460
|
+
const targetDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), targetPosition);
|
|
5461
|
+
this.flyToAnimation = {
|
|
5462
|
+
startDirection,
|
|
5463
|
+
targetDirection,
|
|
5464
|
+
targetPosition,
|
|
5465
|
+
startTime: performance.now(),
|
|
5466
|
+
durationMs: Math.max(1, durationMs),
|
|
5467
|
+
};
|
|
5468
|
+
}
|
|
5469
|
+
updateFlyTo(now) {
|
|
5470
|
+
const animation = this.flyToAnimation;
|
|
5471
|
+
if (!animation) {
|
|
5472
|
+
return false;
|
|
5473
|
+
}
|
|
5474
|
+
const elapsed = now - animation.startTime;
|
|
5475
|
+
const progress = Math.min(1, elapsed / animation.durationMs);
|
|
5476
|
+
const easedProgress = progress < 0.5
|
|
5477
|
+
? 4 * progress * progress * progress
|
|
5478
|
+
: 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
|
5479
|
+
const direction = this.slerpDirection(animation.startDirection, animation.targetDirection, easedProgress);
|
|
5480
|
+
const radius = Math.abs(this.cam_pos[2]);
|
|
5481
|
+
const cameraPosition = gl_matrix_1.vec3.scale(gl_matrix_1.vec3.create(), direction, radius);
|
|
5482
|
+
this.applyCameraPosition(cameraPosition);
|
|
5483
|
+
if (progress >= 1) {
|
|
5484
|
+
this.applyCameraPosition(animation.targetPosition);
|
|
5485
|
+
this.flyToAnimation = null;
|
|
5486
|
+
}
|
|
5487
|
+
return true;
|
|
5488
|
+
}
|
|
5489
|
+
slerpDirection(start, end, t) {
|
|
5490
|
+
const dot = Math.max(-1, Math.min(1, gl_matrix_1.vec3.dot(start, end)));
|
|
5491
|
+
/*
|
|
5492
|
+
* For almost identical directions, normalized linear interpolation is
|
|
5493
|
+
* numerically more stable than the general SLERP formula.
|
|
5494
|
+
*/
|
|
5495
|
+
if (dot > 0.999999) {
|
|
5496
|
+
const result = gl_matrix_1.vec3.lerp(gl_matrix_1.vec3.create(), start, end, t);
|
|
5497
|
+
return gl_matrix_1.vec3.normalize(result, result);
|
|
5498
|
+
}
|
|
5499
|
+
/*
|
|
5500
|
+
* The normal SLERP formula becomes singular for antipodal vectors because
|
|
5501
|
+
* sin(pi) = 0. In that case choose a stable vector orthogonal to start and
|
|
5502
|
+
* rotate through that great circle.
|
|
5503
|
+
*/
|
|
5504
|
+
if (dot < -0.999999) {
|
|
5505
|
+
const referenceAxis = Math.abs(start[0]) < 0.9
|
|
5506
|
+
? gl_matrix_1.vec3.fromValues(1, 0, 0)
|
|
5507
|
+
: gl_matrix_1.vec3.fromValues(0, 1, 0);
|
|
5508
|
+
const orthogonal = gl_matrix_1.vec3.cross(gl_matrix_1.vec3.create(), start, referenceAxis);
|
|
5509
|
+
gl_matrix_1.vec3.normalize(orthogonal, orthogonal);
|
|
5510
|
+
const angle = Math.PI * t;
|
|
5511
|
+
const result = gl_matrix_1.vec3.create();
|
|
5512
|
+
gl_matrix_1.vec3.scale(result, start, Math.cos(angle));
|
|
5513
|
+
gl_matrix_1.vec3.scaleAndAdd(result, result, orthogonal, Math.sin(angle));
|
|
5514
|
+
return gl_matrix_1.vec3.normalize(result, result);
|
|
5515
|
+
}
|
|
5516
|
+
const angle = Math.acos(dot);
|
|
5517
|
+
const sinAngle = Math.sin(angle);
|
|
5518
|
+
const startWeight = Math.sin((1 - t) * angle) / sinAngle;
|
|
5519
|
+
const endWeight = Math.sin(t * angle) / sinAngle;
|
|
5520
|
+
const result = gl_matrix_1.vec3.create();
|
|
5521
|
+
result[0] = start[0] * startWeight + end[0] * endWeight;
|
|
5522
|
+
result[1] = start[1] * startWeight + end[1] * endWeight;
|
|
5523
|
+
result[2] = start[2] * startWeight + end[2] * endWeight;
|
|
5524
|
+
return gl_matrix_1.vec3.normalize(result, result);
|
|
5525
|
+
}
|
|
5526
|
+
cancelFlyTo() {
|
|
5527
|
+
this.flyToAnimation = null;
|
|
5528
|
+
}
|
|
5422
5529
|
toggleInsideSphere() {
|
|
5423
5530
|
// if (inside !== global.insideSphere) {
|
|
5424
5531
|
// global.insideSphere = inside;
|
|
@@ -5676,9 +5783,15 @@ class Camera {
|
|
|
5676
5783
|
clearRotationLock() {
|
|
5677
5784
|
this.lockRotX = this.lockRotY = this.lockRotZ = false;
|
|
5678
5785
|
}
|
|
5679
|
-
isRotationLockedX() {
|
|
5680
|
-
|
|
5681
|
-
|
|
5786
|
+
isRotationLockedX() {
|
|
5787
|
+
return this.lockRotX;
|
|
5788
|
+
}
|
|
5789
|
+
isRotationLockedY() {
|
|
5790
|
+
return this.lockRotY;
|
|
5791
|
+
}
|
|
5792
|
+
isRotationLockedZ() {
|
|
5793
|
+
return this.lockRotZ;
|
|
5794
|
+
}
|
|
5682
5795
|
}
|
|
5683
5796
|
exports["default"] = Camera;
|
|
5684
5797
|
|
|
@@ -7880,6 +7993,7 @@ class Source {
|
|
|
7880
7993
|
_h_pix;
|
|
7881
7994
|
_shapesize;
|
|
7882
7995
|
_brightnessFactor;
|
|
7996
|
+
_mediaStyle;
|
|
7883
7997
|
/**
|
|
7884
7998
|
* @param in_point Point.js (Cartesian/RA-Dec wrapper)
|
|
7885
7999
|
* @param in_details Optional array of key/value metadata
|
|
@@ -7932,6 +8046,12 @@ class Source {
|
|
|
7932
8046
|
set brightnessFactor(factor) {
|
|
7933
8047
|
this._brightnessFactor = factor;
|
|
7934
8048
|
}
|
|
8049
|
+
get mediaStyle() {
|
|
8050
|
+
return this._mediaStyle;
|
|
8051
|
+
}
|
|
8052
|
+
set mediaStyle(style) {
|
|
8053
|
+
this._mediaStyle = style;
|
|
8054
|
+
}
|
|
7935
8055
|
}
|
|
7936
8056
|
exports.Source = Source;
|
|
7937
8057
|
|
|
@@ -8157,6 +8277,9 @@ exports.SphereFoV = SphereFoV;
|
|
|
8157
8277
|
*
|
|
8158
8278
|
* See LICENSE.md, LICENSE-AGPL.md, and LICENSE-COMMERCIAL.md for details.
|
|
8159
8279
|
*/
|
|
8280
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8281
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8282
|
+
};
|
|
8160
8283
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8161
8284
|
exports.CatalogueGL = void 0;
|
|
8162
8285
|
const Source_js_1 = __webpack_require__(146);
|
|
@@ -8165,6 +8288,8 @@ const CoordsType_js_1 = __webpack_require__(8145);
|
|
|
8165
8288
|
const Utils_js_1 = __webpack_require__(7930);
|
|
8166
8289
|
const CatalogueShaderProgram_js_1 = __webpack_require__(3559);
|
|
8167
8290
|
const MetadataManager_js_1 = __webpack_require__(5403);
|
|
8291
|
+
const gl_matrix_1 = __webpack_require__(3890);
|
|
8292
|
+
const Global_js_1 = __importDefault(__webpack_require__(4382));
|
|
8168
8293
|
class CatalogueGL {
|
|
8169
8294
|
_kind = "CatalogueGL";
|
|
8170
8295
|
static ELEM_SIZE = 6;
|
|
@@ -8196,6 +8321,9 @@ class CatalogueGL {
|
|
|
8196
8321
|
_providerUrl;
|
|
8197
8322
|
_catalogueShaderProgram;
|
|
8198
8323
|
_visibleTilesManager;
|
|
8324
|
+
_mediaOverlayHost = null;
|
|
8325
|
+
_mediaElements = [];
|
|
8326
|
+
_svgMediaDataUrls = new Map();
|
|
8199
8327
|
constructor(catalogueName, catalogueDescription, providerUrl, metadataManager, webgl, visibleTilesManager) {
|
|
8200
8328
|
this._webgl = webgl;
|
|
8201
8329
|
this._ready = false;
|
|
@@ -8366,9 +8494,10 @@ class CatalogueGL {
|
|
|
8366
8494
|
* @param in_data Rows of TAP results
|
|
8367
8495
|
* @param columnsmeta TapMetadataList (unused here because `CatalogueProps` already holds indices)
|
|
8368
8496
|
*/
|
|
8369
|
-
addSources(in_data, columnsmeta) {
|
|
8497
|
+
addSources(in_data, columnsmeta, options = {}) {
|
|
8370
8498
|
this._ready = false;
|
|
8371
8499
|
this._sources = [];
|
|
8500
|
+
this.clearMediaOverlay();
|
|
8372
8501
|
this._metadataManager = new MetadataManager_js_1.MetadataManager(columnsmeta);
|
|
8373
8502
|
// const raDataIndex = (this.catalogueProps.raColumn as any).index ?? (this.catalogueProps.raColumn as any)._index;
|
|
8374
8503
|
// const decDataIndex = (this.catalogueProps.decColumn as any).index ?? (this.catalogueProps.decColumn as any)._index;
|
|
@@ -8382,6 +8511,10 @@ class CatalogueGL {
|
|
|
8382
8511
|
decDeg: in_data[j][decDataIndex],
|
|
8383
8512
|
}, CoordsType_js_1.CoordsType.ASTRO);
|
|
8384
8513
|
const source = new Source_js_1.Source(point, in_data[j]);
|
|
8514
|
+
const mediaStyle = this.extractMediaStyle(in_data[j], columnsmeta, options.mediaColumns);
|
|
8515
|
+
if (mediaStyle) {
|
|
8516
|
+
source.mediaStyle = mediaStyle;
|
|
8517
|
+
}
|
|
8385
8518
|
// Ensure optional fields exist
|
|
8386
8519
|
source.shapeSize = source.shapeSize ?? CatalogueGL.STANDARD_SHAPE_SIZE;
|
|
8387
8520
|
source.brightnessFactor = 3;
|
|
@@ -8401,11 +8534,222 @@ class CatalogueGL {
|
|
|
8401
8534
|
this._ready = true;
|
|
8402
8535
|
this._bufferInitialised = false;
|
|
8403
8536
|
}
|
|
8537
|
+
getColumnIndex(columnsmeta, columnName) {
|
|
8538
|
+
if (!columnName)
|
|
8539
|
+
return -1;
|
|
8540
|
+
return columnsmeta.find((column) => column.name === columnName)?.index ?? -1;
|
|
8541
|
+
}
|
|
8542
|
+
readCell(row, columnsmeta, columnName) {
|
|
8543
|
+
const idx = this.getColumnIndex(columnsmeta, columnName);
|
|
8544
|
+
return idx >= 0 ? row[idx] : undefined;
|
|
8545
|
+
}
|
|
8546
|
+
extractMediaStyle(row, columnsmeta, mediaColumns) {
|
|
8547
|
+
if (!mediaColumns?.src)
|
|
8548
|
+
return undefined;
|
|
8549
|
+
const rawSrc = this.readCell(row, columnsmeta, mediaColumns.src);
|
|
8550
|
+
const src = String(rawSrc ?? "").trim();
|
|
8551
|
+
if (!src)
|
|
8552
|
+
return undefined;
|
|
8553
|
+
const rawKind = String(this.readCell(row, columnsmeta, mediaColumns.type) ?? "sprite").trim().toLowerCase();
|
|
8554
|
+
const kind = rawKind === "model"
|
|
8555
|
+
? "model"
|
|
8556
|
+
: rawKind === "image"
|
|
8557
|
+
? "image"
|
|
8558
|
+
: rawKind === "icon"
|
|
8559
|
+
? "icon"
|
|
8560
|
+
: rawKind === "circle"
|
|
8561
|
+
? "circle"
|
|
8562
|
+
: "sprite";
|
|
8563
|
+
if (kind === "circle" || kind === "model")
|
|
8564
|
+
return undefined;
|
|
8565
|
+
const scale = Number(this.readCell(row, columnsmeta, mediaColumns.scale));
|
|
8566
|
+
const rotationDeg = Number(this.readCell(row, columnsmeta, mediaColumns.rotation));
|
|
8567
|
+
const opacity = Number(this.readCell(row, columnsmeta, mediaColumns.opacity));
|
|
8568
|
+
return {
|
|
8569
|
+
kind,
|
|
8570
|
+
src,
|
|
8571
|
+
scale: Number.isFinite(scale) && scale > 0 ? scale : undefined,
|
|
8572
|
+
rotationDeg: Number.isFinite(rotationDeg) ? rotationDeg : undefined,
|
|
8573
|
+
opacity: Number.isFinite(opacity) ? Math.max(0, Math.min(1, opacity)) : undefined,
|
|
8574
|
+
};
|
|
8575
|
+
}
|
|
8404
8576
|
clearSources() {
|
|
8405
8577
|
this._sources = [];
|
|
8406
8578
|
this.hoveredIndexes = [];
|
|
8407
8579
|
this._healpixDensityMap.clear();
|
|
8408
8580
|
this.vertexCataloguePosition = new Float32Array(0);
|
|
8581
|
+
this.clearMediaOverlay();
|
|
8582
|
+
}
|
|
8583
|
+
clearMediaOverlay() {
|
|
8584
|
+
this._mediaElements.forEach((el) => el.remove());
|
|
8585
|
+
this._mediaElements = [];
|
|
8586
|
+
if (this._mediaOverlayHost) {
|
|
8587
|
+
this._mediaOverlayHost.remove();
|
|
8588
|
+
this._mediaOverlayHost = null;
|
|
8589
|
+
}
|
|
8590
|
+
}
|
|
8591
|
+
ensureMediaOverlayHost() {
|
|
8592
|
+
const canvas = this._webgl?.canvas;
|
|
8593
|
+
const parent = canvas?.parentElement;
|
|
8594
|
+
if (!canvas || !parent)
|
|
8595
|
+
return null;
|
|
8596
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
8597
|
+
if (parentStyle.position === "static") {
|
|
8598
|
+
parent.style.position = "relative";
|
|
8599
|
+
}
|
|
8600
|
+
if (!this._mediaOverlayHost) {
|
|
8601
|
+
const host = document.createElement("div");
|
|
8602
|
+
host.dataset.astroCatalogueMedia = "true";
|
|
8603
|
+
host.style.position = "absolute";
|
|
8604
|
+
host.style.left = `${canvas.offsetLeft}px`;
|
|
8605
|
+
host.style.top = `${canvas.offsetTop}px`;
|
|
8606
|
+
host.style.width = `${canvas.clientWidth}px`;
|
|
8607
|
+
host.style.height = `${canvas.clientHeight}px`;
|
|
8608
|
+
host.style.pointerEvents = "none";
|
|
8609
|
+
host.style.overflow = "hidden";
|
|
8610
|
+
host.style.zIndex = "4";
|
|
8611
|
+
canvas.style.position = canvas.style.position || "relative";
|
|
8612
|
+
canvas.style.zIndex = canvas.style.zIndex || "0";
|
|
8613
|
+
parent.appendChild(host);
|
|
8614
|
+
this._mediaOverlayHost = host;
|
|
8615
|
+
}
|
|
8616
|
+
else {
|
|
8617
|
+
this._mediaOverlayHost.style.left = `${canvas.offsetLeft}px`;
|
|
8618
|
+
this._mediaOverlayHost.style.top = `${canvas.offsetTop}px`;
|
|
8619
|
+
this._mediaOverlayHost.style.width = `${canvas.clientWidth}px`;
|
|
8620
|
+
this._mediaOverlayHost.style.height = `${canvas.clientHeight}px`;
|
|
8621
|
+
}
|
|
8622
|
+
return this._mediaOverlayHost;
|
|
8623
|
+
}
|
|
8624
|
+
ensureMediaElement(index, style) {
|
|
8625
|
+
let img = this._mediaElements[index];
|
|
8626
|
+
if (!img) {
|
|
8627
|
+
img = document.createElement("img");
|
|
8628
|
+
img.loading = "lazy";
|
|
8629
|
+
img.decoding = "async";
|
|
8630
|
+
img.style.position = "absolute";
|
|
8631
|
+
img.style.objectFit = "contain";
|
|
8632
|
+
img.style.pointerEvents = "none";
|
|
8633
|
+
img.style.transformOrigin = "center center";
|
|
8634
|
+
img.style.filter = "drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55))";
|
|
8635
|
+
img.style.willChange = "transform, left, top";
|
|
8636
|
+
this._mediaElements[index] = img;
|
|
8637
|
+
}
|
|
8638
|
+
const src = String(style.src ?? "").trim();
|
|
8639
|
+
const resolvedSrc = src ? new URL(src, document.baseURI).href : "";
|
|
8640
|
+
if (resolvedSrc && img.getAttribute("src") !== resolvedSrc) {
|
|
8641
|
+
img.removeAttribute("data-astro-media-src-index");
|
|
8642
|
+
img.removeAttribute("data-astro-media-loaded");
|
|
8643
|
+
img.onerror = null;
|
|
8644
|
+
img.onload = null;
|
|
8645
|
+
img.setAttribute("src", resolvedSrc);
|
|
8646
|
+
if (/\.svg(?:[?#].*)?$/i.test(resolvedSrc)) {
|
|
8647
|
+
void this.loadSvgMediaAsDataUrl(img, resolvedSrc);
|
|
8648
|
+
}
|
|
8649
|
+
}
|
|
8650
|
+
return img;
|
|
8651
|
+
}
|
|
8652
|
+
async loadSvgMediaAsDataUrl(img, resolvedSrc) {
|
|
8653
|
+
try {
|
|
8654
|
+
let dataUrl = this._svgMediaDataUrls.get(resolvedSrc);
|
|
8655
|
+
if (!dataUrl) {
|
|
8656
|
+
const response = await fetch(resolvedSrc);
|
|
8657
|
+
if (!response.ok)
|
|
8658
|
+
return;
|
|
8659
|
+
const svg = await response.text();
|
|
8660
|
+
if (!/^\s*<svg[\s>]/i.test(svg))
|
|
8661
|
+
return;
|
|
8662
|
+
dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
8663
|
+
this._svgMediaDataUrls.set(resolvedSrc, dataUrl);
|
|
8664
|
+
}
|
|
8665
|
+
if (img.getAttribute("src") === resolvedSrc) {
|
|
8666
|
+
img.setAttribute("src", dataUrl);
|
|
8667
|
+
}
|
|
8668
|
+
}
|
|
8669
|
+
catch {
|
|
8670
|
+
// Keep the direct image src; remote servers may already provide a valid MIME type.
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
isSourceOnVisibleHemisphere(source, in_mMatrix, vMatrix) {
|
|
8674
|
+
if (Global_js_1.default.insideSphere)
|
|
8675
|
+
return true;
|
|
8676
|
+
const invView = gl_matrix_1.mat4.create();
|
|
8677
|
+
if (!gl_matrix_1.mat4.invert(invView, vMatrix))
|
|
8678
|
+
return true;
|
|
8679
|
+
const cameraPos = gl_matrix_1.vec3.fromValues(invView[12], invView[13], invView[14]);
|
|
8680
|
+
const worldPoint4 = gl_matrix_1.vec4.fromValues(source.point.x, source.point.y, source.point.z, 1);
|
|
8681
|
+
gl_matrix_1.vec4.transformMat4(worldPoint4, worldPoint4, in_mMatrix);
|
|
8682
|
+
const worldPoint = gl_matrix_1.vec3.fromValues(worldPoint4[0], worldPoint4[1], worldPoint4[2]);
|
|
8683
|
+
const normal = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), worldPoint);
|
|
8684
|
+
const toCamera = gl_matrix_1.vec3.subtract(gl_matrix_1.vec3.create(), cameraPos, worldPoint);
|
|
8685
|
+
return gl_matrix_1.vec3.dot(normal, toCamera) > 0;
|
|
8686
|
+
}
|
|
8687
|
+
updateMediaOverlay(in_mMatrix, vMatrix, pMatrix) {
|
|
8688
|
+
const sourcesWithMedia = this._sources.filter((source) => !!source.mediaStyle?.src);
|
|
8689
|
+
if (!sourcesWithMedia.length) {
|
|
8690
|
+
this.clearMediaOverlay();
|
|
8691
|
+
return;
|
|
8692
|
+
}
|
|
8693
|
+
const host = this.ensureMediaOverlayHost();
|
|
8694
|
+
const canvas = this._webgl.canvas;
|
|
8695
|
+
if (!host || !canvas.clientWidth || !canvas.clientHeight)
|
|
8696
|
+
return;
|
|
8697
|
+
const modelView = gl_matrix_1.mat4.create();
|
|
8698
|
+
const mvp = gl_matrix_1.mat4.create();
|
|
8699
|
+
gl_matrix_1.mat4.multiply(modelView, vMatrix, in_mMatrix);
|
|
8700
|
+
gl_matrix_1.mat4.multiply(mvp, pMatrix, modelView);
|
|
8701
|
+
const width = canvas.clientWidth;
|
|
8702
|
+
const height = canvas.clientHeight;
|
|
8703
|
+
for (let i = 0; i < this._sources.length; i++) {
|
|
8704
|
+
const source = this._sources[i];
|
|
8705
|
+
const style = source.mediaStyle;
|
|
8706
|
+
const img = this._mediaElements[i];
|
|
8707
|
+
if (!style?.src) {
|
|
8708
|
+
if (img)
|
|
8709
|
+
img.style.display = "none";
|
|
8710
|
+
continue;
|
|
8711
|
+
}
|
|
8712
|
+
if (!this.isSourceOnVisibleHemisphere(source, in_mMatrix, vMatrix)) {
|
|
8713
|
+
if (img)
|
|
8714
|
+
img.style.display = "none";
|
|
8715
|
+
continue;
|
|
8716
|
+
}
|
|
8717
|
+
const projected = gl_matrix_1.vec4.fromValues(source.point.x, source.point.y, source.point.z, 1);
|
|
8718
|
+
gl_matrix_1.vec4.transformMat4(projected, projected, mvp);
|
|
8719
|
+
if (!projected[3]) {
|
|
8720
|
+
if (img)
|
|
8721
|
+
img.style.display = "none";
|
|
8722
|
+
continue;
|
|
8723
|
+
}
|
|
8724
|
+
const ndcX = projected[0] / projected[3];
|
|
8725
|
+
const ndcY = projected[1] / projected[3];
|
|
8726
|
+
const ndcZ = projected[2] / projected[3];
|
|
8727
|
+
if (ndcX < -1.15 ||
|
|
8728
|
+
ndcX > 1.15 ||
|
|
8729
|
+
ndcY < -1.15 ||
|
|
8730
|
+
ndcY > 1.15 ||
|
|
8731
|
+
ndcZ < -1.15 ||
|
|
8732
|
+
ndcZ > 1.15) {
|
|
8733
|
+
if (img)
|
|
8734
|
+
img.style.display = "none";
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
const mediaEl = this.ensureMediaElement(i, style);
|
|
8738
|
+
if (!mediaEl.parentElement)
|
|
8739
|
+
host.appendChild(mediaEl);
|
|
8740
|
+
const px = (ndcX * 0.5 + 0.5) * width;
|
|
8741
|
+
const py = (1 - (ndcY * 0.5 + 0.5)) * height;
|
|
8742
|
+
const baseSize = source.shapeSize || CatalogueGL.STANDARD_SHAPE_SIZE;
|
|
8743
|
+
const size = Math.max(8, baseSize * (style.scale ?? 2.4));
|
|
8744
|
+
const rotation = style.rotationDeg ?? 0;
|
|
8745
|
+
mediaEl.style.display = "block";
|
|
8746
|
+
mediaEl.style.left = `${px - size / 2}px`;
|
|
8747
|
+
mediaEl.style.top = `${py - size / 2}px`;
|
|
8748
|
+
mediaEl.style.width = `${size}px`;
|
|
8749
|
+
mediaEl.style.height = `${size}px`;
|
|
8750
|
+
mediaEl.style.opacity = `${style.opacity ?? 1}`;
|
|
8751
|
+
mediaEl.style.transform = `rotate(${rotation}deg)`;
|
|
8752
|
+
}
|
|
8409
8753
|
}
|
|
8410
8754
|
sourceMatches(left, right) {
|
|
8411
8755
|
if (left === right)
|
|
@@ -8745,6 +9089,7 @@ class CatalogueGL {
|
|
|
8745
9089
|
// draw
|
|
8746
9090
|
const numItems = this.vertexCataloguePosition.length / CatalogueGL.ELEM_SIZE;
|
|
8747
9091
|
this._webgl.drawArrays(this._webgl.POINTS, 0, numItems);
|
|
9092
|
+
this.updateMediaOverlay(in_mMatrix, vMatrix, pMatrix);
|
|
8748
9093
|
this._oldMouseCoords = in_mouseHelper.xyz;
|
|
8749
9094
|
}
|
|
8750
9095
|
}
|