Compare commits

..

35 Commits

Author SHA1 Message Date
Matthieu Baumann
2ef5e47c27 update changelog 2026-01-07 11:39:10 +01:00
Matthieu Baumann
337618d6ef remove logs 2026-01-07 10:09:13 +01:00
Matthieu Baumann
832e8cd4bd bugfix allsky fits not appearing 2026-01-07 10:05:04 +01:00
Matthieu Baumann
28869645cd fix opacity restore in UI, and screen size shrinking 2025-12-12 11:27:03 +01:00
Matthieu Baumann
da8eb6f76e feat: swap layer orders 2025-12-08 17:46:34 +01:00
Matthieu Baumann
0e3a359108 wip base layer 2025-12-08 14:33:31 +01:00
Matthieu Baumann
af89535a91 first commit tree 2025-12-08 14:33:29 +01:00
Matthieu Baumann
8d244596ba 3.7.3-beta 2025-11-05 15:16:36 +01:00
Matthieu Baumann
2a23e83c13 update fitsrs version 2025-10-20 14:41:30 +02:00
Matthieu Baumann
7b8272795d cargo clippy 2025-10-20 10:12:58 +02:00
Matthieu Baumann
4d8b4bfb21 Many fixes
* fix: HiPS3D probing spectra in galactic frame
* ui: new more option in the HiPS selector allowing to change the base layer with an arbitrary HiPS from the UI
* fits: support tile compressed image in bintable extensions (SRCNet feature: https://jira.skatelescope.org/browse/MAN-559)
2025-10-20 09:59:49 +02:00
Matthieu Baumann
ebf8845e83 fix opacity param for PolyLines #316 2025-09-29 15:35:02 +02:00
Matthieu Baumann
f863ac902c fix 0.5 pixel offset when plotting FITS image files 2025-09-29 14:29:38 +02:00
Matthieu Baumann
75123e6bc8 first commit 2025-09-25 10:37:40 +02:00
Matthieu Baumann
d5d7d2a650 3.7.2 2025-09-25 10:36:14 +02:00
Matthieu Baumann
d22c25ea8a add npm deploy routine in package json 2025-09-24 17:13:24 +02:00
Matthieu Baumann
acef664b45 cargo clippy 2025-09-24 17:08:32 +02:00
Matthieu Baumann
9e8db0379b cargo clippy & fmt 2025-09-24 16:59:31 +02:00
Matthieu Baumann
032bb57517 rotation improve perf and numerical instabilities 2025-09-23 17:20:16 +02:00
Matthieu Baumann
f0fc39d2c8 drag outside the projection domain 2025-09-22 15:38:58 +02:00
Matthieu Baumann
2df32cb643 change u-strasbg to cds.unistra urls + enhance/simplify inertia effect 2025-09-22 11:11:12 +02:00
Matthieu Baumann
904d449006 fix: setCuts with imageFormat, Add more HiPS search 2025-09-19 13:59:25 +02:00
Matthieu Baumann
2594aff1b6 fix: crossOrigin set for HtmlImageElement from requestCredentials 2025-09-17 17:02:40 +02:00
Matthieu Baumann
547c5422d4 add minified shaders 2025-09-17 14:26:45 +02:00
Matthieu Baumann
9bcc93877b change changelog 2025-09-15 14:11:52 +02:00
Matthieu Baumann
3f6f247735 add changelog 3.7.0 2025-09-15 11:58:51 +02:00
Matthieu Baumann
c6c7ad44c9 add a prod routine only for internal production deployment on our server. npm run serve should behave like before 2025-09-12 18:04:08 +02:00
Matthieu Baumann
cdc1733c4f Add label to checkbox UI filter enabler 2025-09-12 11:05:32 +02:00
Matthieu Baumann
6e40dbbfc1 update testing snapshots 2025-09-11 17:52:27 +02:00
Matthieu Baumann
e03b16119b several fixes: panic when delaying resources treatments + ICRS sexa 2025-09-11 17:49:05 +02:00
Matthieu Baumann
e3162426be fix: restore polyselect 2025-09-11 15:35:39 +02:00
Matthieu Baumann
5a285dabed cargo clippy 2025-09-11 15:00:53 +02:00
Matthieu Baumann
2d04730623 fix: read pixel for jpg (works like rgba) and fits (1px offset fix + switch bytes order) 2025-09-11 14:31:45 +02:00
bmatthieu3
390c9096d7 NED tap access 2025-09-10 11:52:15 +02:00
Thomas Boch
0e9998a7fc Merge pull request #327 from cds-astro/customize-share-url-function
Customize share URL function
2025-09-10 11:22:42 +02:00
98 changed files with 10046 additions and 1223 deletions

View File

@@ -32,7 +32,7 @@ jobs:
npm install
- name: "Build Aladin Lite"
run: |
npm run build
npm run build:npm
- name: "Publish Aladin Lite to npm"
run: |
npm publish

8
.gitignore vendored
View File

@@ -15,9 +15,11 @@ package-lock.json
src/core/Cargo.lock
src/core/target/
# this rust file is generated when compiling the code, so it is not
# useful to put it on git
src/core/src/shaders.rs
# the tmp glsl files used when minifying the shaders into the wasm (build.rs)
src/glsl/webgl2/**/*.min
src/glsl/webgl2/**/*.tmp
package/
## python related
# python environment

View File

@@ -4,11 +4,40 @@
### What's Changed
* [perf] perform CPU computations with Vec3 and Matrix3 and not 4 dimensions matrices/vectors
* [feat] lockNorthUp Aladin object new option locking the north pole up to the view center
## Released
### 3.8.0
* [fix] horizontal/vertical overlay lines appearing correctly <https://github.com/cds-astro/aladin-lite/issues/334>
* [fix] layer opacity restored when switching from not visible to visible <https://github.com/cds-astro/aladin-lite/issues/332>
* [feat] dark/light mode for the interface
* [fix] polylines shapes size not consistent w.r.t to div size <https://github.com/cds-astro/aladin-lite/issues/331>
### 3.7.0-beta
#### What's Changed
* [feat] flip longitude axis global method on Aladin by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/245>
* [feat] add rotation event by [@ManonMarchand][ManonMarchand] in <https://github.com/cds-astro/aladin-lite/pull/283>
* [docs] just fixing typo in image's doc by [@ManonMarchand][ManonMarchand] in <https://github.com/cds-astro/aladin-lite/pull/284>
* [docs] change to an image with correct astrometry in example by [@ManonMarchand][ManonMarchand] in <https://github.com/cds-astro/aladin-lite/pull/290>
* [docs] clarify use of precision in Coo by [@alexgoff][alexgoff] in <https://github.com/cds-astro/aladin-lite/pull/294>
* [feat] allow setting HiPS CORS and credential options by [@pmatsson][pmatsson] in <https://github.com/cds-astro/aladin-lite/pull/281>
* [feat] color picker and read pixel(s) API methods by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/280>
* [fix] chandra hips display and akari by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/306>
* [enhancement] update to the new version of fitsrs by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/310>
* [fix] 26 channel color offset by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/320>
* [fix] Circle intersectsBBox by [@emellega][emellega] in <https://github.com/cds-astro/aladin-lite/pull/309>
* [feat] anti aliasing on lines plotted by the GPU by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/239>
* [feat] source custom color and size from its data content by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/321>
* [feat] catalog new select method by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/322>
* [feat] HiPS 3D impl by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/314>
* [feat] customize share URL function by [@tboch][tboch] in <https://github.com/cds-astro/aladin-lite/pull/327>
* enhancement: use TAP entry point to query NED by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/257>
* [perf] refac geometrical computations using Vec3/Mat3 instead of Vec4/Mat4 [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/276>
* [fix] distortion at poles by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/247>
* [feat] new aladin option `lockNorthUp` to keep north pole up [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/272>
### 3.6.3
#### What's Changed

View File

@@ -1,4 +1,4 @@
# [Aladin Lite](https://aladin.u-strasbg.fr/AladinLite)
# [Aladin Lite](https://aladin.cds.unistra.fr/AladinLite)
**An astronomical HiPS visualizer in the browser** <img src="aladin-logo.png" alt="Aladin Lite logo" width="220">
@@ -8,14 +8,14 @@ See [A&A 578, A114 (2015)](https://arxiv.org/abs/1505.02291) and [IVOA HiPS Reco
Aladin Lite is built to be easily embeddable in any web page. It powers astronomical portals like [ESASky](https://sky.esa.int/), [ESO Science Archive portal](http://archive.eso.org/scienceportal/) and [ALMA Portal](https://almascience.eso.org/asax/).
More details on [Aladin Lite documentation page](http://aladin.u-strasbg.fr/AladinLite/doc/).
More details on [Aladin Lite documentation page](http://aladin.cds.unistra.fr/AladinLite/doc/).
A new [API technical documentation](https://cds-astro.github.io/aladin-lite/) is now available.
[![Run tests](https://github.com/cds-astro/aladin-lite/actions/workflows/test.yml/badge.svg)](https://github.com/cds-astro/aladin-lite/actions/workflows/test.yml)
[![API Documentation](https://img.shields.io/badge/API-documentation-blue.svg)](https://cds-astro.github.io/aladin-lite)
[![Release page](https://img.shields.io/badge/Release-download-yellow.svg)](https://aladin.cds.unistra.fr/AladinLite/doc/release/)
Try Aladin Lite [here](https://aladin.u-strasbg.fr/AladinLite).
Try Aladin Lite [here](https://aladin.cds.unistra.fr/AladinLite).
Aladin Lite is made possible thanks to pure Rust core libraries:
* [cdshealpix](https://github.com/cds-astro/cds-healpix-rust) - for HEALPix projection and unprojection to/from sky coordinates

4
assets/icons/folder.svg Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1H5L8 3H13V5H3.7457L2.03141 11H4.11144L5.2543 7H16L14 14H0V1Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 322 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.7955 15.8111L21 21M18 10.5C18 14.6421 14.6421 18 10.5 18C6.35786 18 3 14.6421 3 10.5C3 6.35786 6.35786 3 10.5 3C14.6421 3 18 6.35786 18 10.5Z"
stroke="#ffffff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 393 B

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7955 15.8111L21 21M18 10.5C18 14.6421 14.6421 18 10.5 18C6.35786 18 3 14.6421 3 10.5C3 6.35786 6.35786 3 10.5 3C14.6421 3 18 6.35786 18 10.5Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 455 B

View File

@@ -1,4 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7955 15.8111L21 21M18 10.5C18 14.6421 14.6421 18 10.5 18C6.35786 18 3 14.6421 3 10.5C3 6.35786 6.35786 3 10.5 3C14.6421 3 18 6.35786 18 10.5Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.7955 15.8111L21 21M18 10.5C18 14.6421 14.6421 18 10.5 18C6.35786 18 3 14.6421 3 10.5C3 6.35786 6.35786 3 10.5 3C14.6421 3 18 6.35786 18 10.5Z"
stroke="#000000"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 393 B

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.1546 3.36026C12.4835 3.00999 11.5741 3.00459 10.8976 3.35396C8.30466 4.6931 5.95279 6.22853 3.89182 7.93154C3.57311 8.19489 3.34693 8.57758 3.35079 9.02802C3.35463 9.47662 3.58541 9.85419 3.90218 10.1132C5.94604 11.7844 8.29985 13.3212 10.8453 14.6497C11.5165 15 12.4258 15.0054 13.1023 14.656C15.6953 13.3169 18.0472 11.7815 20.1081 10.0785C20.4268 9.8151 20.653 9.43242 20.6492 8.98197C20.6453 8.53338 20.4145 8.1558 20.0978 7.89679C18.0539 6.22562 15.7001 4.6888 13.1546 3.36026ZM11.5859 4.68671C11.8256 4.56294 12.2193 4.56411 12.4606 4.69004C14.8899 5.95796 17.1283 7.41666 19.0675 8.99223C17.1167 10.5932 14.885 12.0471 12.414 13.3233C12.1744 13.4471 11.7807 13.4459 11.5394 13.32C9.11004 12.052 6.87163 10.5933 4.9324 9.01777C6.88321 7.41684 9.11496 5.96285 11.5859 4.68671Z" fill="#000000"/>
<path d="M21.197 12.698C21.4164 13.0494 21.3094 13.512 20.958 13.7314L14.8508 17.5443C14.022 18.0617 12.9938 18.3009 11.9999 18.301C11.006 18.301 9.9777 18.0619 9.14884 17.5446L3.10851 13.7749C2.75711 13.5556 2.65003 13.093 2.86934 12.7416C3.08864 12.3902 3.55128 12.2831 3.90268 12.5024L9.94301 16.2721C10.4872 16.6117 11.2264 16.801 11.9998 16.801C12.7732 16.8009 13.5124 16.6116 14.0564 16.2719L20.1636 12.459C20.515 12.2397 20.9776 12.3467 21.197 12.698Z" fill="#000000"/>
<path d="M21.197 16.4527C21.4164 16.804 21.3094 17.2667 20.9581 17.4861L15.6692 20.7889C14.6115 21.4494 13.2886 21.7602 11.9998 21.7602C10.7111 21.7603 9.38808 21.4497 8.3303 20.7894L3.10843 17.5296C2.75706 17.3102 2.65004 16.8476 2.86938 16.4962C3.08873 16.1448 3.55139 16.0378 3.90276 16.2572L9.12462 19.517C9.89764 19.9995 10.9316 20.2603 11.9998 20.2602C13.068 20.2602 14.1018 19.9993 14.8746 19.5167L20.1635 16.2138C20.5149 15.9944 20.9776 16.1013 21.197 16.4527Z" fill="#000000"/>
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" version="1.1" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="m1.75 11 6.25 3.25 6.25-3.25m-12.5-3 6.25 3.25 6.25-3.25m-6.25-6.25-6.25 3.25 6.25 3.25 6.25-3.25z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 437 B

15
assets/icons/swap.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>swap-vertical-circle</title>
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
</g>
<g id="icons_Q2" data-name="icons Q2">
<path d="M19.4,36.4l5-4.9a2.1,2.1,0,0,0,.2-2.7,1.9,1.9,0,0,0-3-.2L20,30.2V15a2,2,0,0,0-4,0V30.2l-1.6-1.6a1.9,1.9,0,0,0-3,.2,2.1,2.1,0,0,0,.2,2.7l5,4.9A1.9,1.9,0,0,0,19.4,36.4Z"/>
<path d="M32,33V17.8l1.6,1.6a1.9,1.9,0,0,0,3-.2,2.1,2.1,0,0,0-.2-2.7l-5-4.9a1.9,1.9,0,0,0-2.8,0l-5,4.9a2.1,2.1,0,0,0-.2,2.7,1.9,1.9,0,0,0,3,.2L28,17.8V33a2,2,0,0,0,4,0Z"/>
<path d="M24,42A18,18,0,1,1,42,24,18.1,18.1,0,0,1,24,42m0,4A22,22,0,1,0,2,24,21.9,21.9,0,0,0,24,46Z"/>
</g>
</g>

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -8,8 +8,8 @@
"dateModified": "2023-01-31",
"issueTracker": "https://github.com/cds-astro/aladin-lite/issues",
"name": "Aladin Lite",
"version": "3.6.5",
"softwareVersion": "3.6.5",
"version": "3.7.3-beta",
"softwareVersion": "3.7.3-beta",
"description": "An astronomical HiPS visualizer in the browser.",
"identifier": "10.5281/zenodo.7638833",
"applicationCategory": "Astronomy, Visualization",

View File

@@ -30,7 +30,7 @@
fov *= 0.997;
rotation += 0.07;
aladin.setViewCenter2NorthPoleAngle(rotation)
aladin.setRotation(rotation)
aladin.setFoV(fov);
if (fov < 3 && fov > 0.5) {

View File

@@ -7,7 +7,7 @@
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {fov: 70,projection: "AIT"});
let aladin = A.aladin('#aladin-lite-div', {target: "23 28 32.46 -00 10 38.9", fov: 4, projection: "AIT"});
let hsc = aladin.newImageSurvey("P/HSC/DR2/deep/g", {colormap:"Purples", imgFormat: "fits"});
aladin.setBaseImageLayer(hsc);

View File

@@ -11,7 +11,7 @@
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: 'P/DSS2/red', target: 'M50', fov: 0.3});
aladin = A.aladin('#aladin-lite-div', {survey: 'P/DSS2/red', target: '05 23 07.72 -69 46 09.1', fov: 0.3});
var customImg = new Image();
customImg.onload = function() {

View File

@@ -16,8 +16,6 @@
fov: 10,
showContextMenu: true,
fullScreen: true,
showSimbadPointerControl: true,
showShareControl: true,
showSettingsControl: true,
showStackLayerControl: true,
samp: true,

View File

@@ -37,7 +37,10 @@
colorPicker.value = cat.color;
colorPicker.addEventListener('input', function (e) {
// Change the color of the catalog
cat.updateShape({color: this.value});
console.log(this.value)
cat.updateShape({color: () => {
return '#00ff00'
}});
})
// Define the box

View File

@@ -10,12 +10,12 @@
import A from '../src/js/A.js';
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {projection: 'AIT', cooFrame: 'galactic', fov: 200, target: 'galactic center'});
aladin = A.aladin('#aladin-lite-div', {projection: 'AIT', cooFrame: 'galactic', fov: 200, target: 'galactic center', showSettingsControl: true, showCooGridControl: true});
let dss = aladin.createImageSurvey("DSS blue band", "Color DSS blue HiPS", "http://alasky.cds.unistra.fr/DSS/DSS2-blue-XJ-S/", "equatorial", 9, {imgFormat: 'fits'})
aladin.setBaseImageLayer(dss);
dss.setCuts(2, 10000);
dss.setCuts(2, 100, 'jpeg');
});

View File

@@ -12,7 +12,7 @@
A.init.then(() => {
// Start up Aladin Lite
aladin = A.aladin('#aladin-lite-div', {target: 'M 1', fov: 0.2, showContextMenu: true, fullScreen: true});
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 3, lineDash: [2, 2]});
var overlay = A.graphicOverlay({color: 'purple', lineWidth: 3, lineDash: [2, 2]});
aladin.addOverlay(overlay);
overlay.addFootprints([
A.polygon([[83.64287, 22.01713], [83.59872, 22.01692], [83.59852, 21.97629], [83.64295, 21.97629]], {hoverColor: 'green'}),

View File

@@ -16,7 +16,7 @@
showSimbadPointerControl: true,
projection: 'AIT', // set a projection
fov: 8.0, // initial field of view in degrees
target: '00 42 21.37 +41 07 29.8', // initial target
target: '10.6875598 +41.1402170', // initial target
cooFrame: 'icrs', // set galactic frame
reticleColor: '#ff89ff', // change reticle color
showContextMenu: true,
@@ -28,7 +28,7 @@
}
);
hips = aladin.newImageSurvey("http://alasky.cds.unistra.fr/HIPS3D/GalfaHI", {
hips = aladin.newImageSurvey("https://alasky.cds.unistra.fr/HIPS3D/LGLBSHI-test-compression/", {
successCallback: (hips) => {
//hips.setFrequency({value: 6.374279333565797E-7, unit: "m"}) // GALFA
}

View File

@@ -11,7 +11,7 @@
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: "data/hips/CDS_P_DSS2_color", target: "05 40 59.12 -02 27 04.1", fov: 2, log: false});
aladin = A.aladin('#aladin-lite-div', {survey: "./data/hips/CDS_P_DSS2_color", target: "05 40 59.12 -02 27 04.1", fov: 2, log: false});
});
</script>

View File

@@ -7,31 +7,55 @@
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {fov: 30, survey: "CDS/P/GALEXGR6/AIS/FUV", target: "280 +0", projection: "AIT", showShareControl:true, showSettingsControl: true, showContextMenu:true});
let aladin = A.aladin('#aladin-lite-div', {fov: 30, target: "286.411023328 -37.3460065319", projection: "AIT", showShareControl:true, showSettingsControl: true, showContextMenu:true});
aladin.setOverlayImageLayer(A.image(
"https://nova.astrometry.net/image/25038473?filename=M61.jpg",
"data/img/m82.png",
{
name: "M61",
name: "M82",
wcs: {
NAXIS: 2, // Minimal header
CTYPE1: 'RA---TAN', // TAN (gnomic) projection
CTYPE2: 'DEC--TAN', // TAN (gnomic) projection
NAXIS: 2, // number of axes
NAXIS1: 3000, // image width
NAXIS2: 1918, // image height
CTYPE3: "RGB", // Tell Aladin this is RGB
WCSAXES: 2, // no comment
CTYPE1: "RA---TAN", // TAN (gnomic) projection + SIP distortions
CTYPE2: "DEC--TAN", // TAN (gnomic) projection + SIP distortions
EQUINOX: 2000.0, // Equatorial coordinates definition (yr)
LONPOLE: 180.0, // no comment
LATPOLE: 0.0, // no comment
CRVAL1: 185.445488837, // RA of reference point
CRVAL2: 4.47896032431, // DEC of reference point
CRPIX1: 588.995094299, // X reference pixel
CRPIX2: 308.307905197, // Y reference pixel
CUNIT1: 'deg', // X pixel scale units
CUNIT2: 'deg', // Y pixel scale units
CD1_1: -0.000223666022989, // Transformation matrix
CD1_2: -0.000296578064584, // no comment
CD2_1: -0.000296427555509, // no comment
CD2_2: 0.000223774308964, // no comment
NAXIS1: 1080, // Image width, in pixels.
NAXIS2: 705 // Image height, in pixels.
CRVAL1: 286.411023328, // RA of reference point
CRVAL2: -37.3460065319, // DEC of reference point
CRPIX1: 2264.1858724, // X reference pixel
CRPIX2: 583.14634196, // Y reference pixel
CUNIT1: "deg", // X pixel scale units
CUNIT2: "deg", // Y pixel scale units
CD1_1: -0.00284225200648, // Transformation matrix
CD1_2: 0.00145908284254, // no comment
CD2_1: -0.00145832184852, // no comment
CD2_2: -0.0028440175499, // no comment
A_ORDER: 2, // Polynomial order, axis 1
A_0_0: 0, A_0_1: 0, A_0_2: 1.97760279295e-7,
A_1_0: 0, A_1_1: 5.32298396638e-7,
A_2_0: -2.16045473726e-6,
B_ORDER: 2, // Polynomial order, axis 2
B_0_0: 0, B_0_1: 0, B_0_2: 3.97377848239e-7,
B_1_0: 0, B_1_1: -2.25823401545e-6,
B_2_0: -1.47800507759e-7,
AP_ORDER: 2, // Inv polynomial order, axis 1
AP_0_0: 0.00617616810622,
AP_0_1: -1.68315582233e-6,
AP_0_2: -1.96504899588e-7,
AP_1_0: -5.8320637913e-6,
AP_1_1: -5.26081207663e-7,
AP_2_0: 2.13760782681e-6,
BP_ORDER: 2, // Inv polynomial order, axis 2
BP_0_0: -0.000681183014773,
BP_0_1: -2.15389849968e-7,
BP_0_2: -3.94508397022e-7,
BP_1_0: 4.51837961352e-6,
BP_1_1: 2.24050293101e-6,
BP_2_0: 1.49195269783e-7
},
successCallback: (ra, dec, fov, image) => {
aladin.gotoRaDec(ra, dec);

View File

@@ -8,7 +8,7 @@
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', fullScreen: true, fov: 360, survey: ['P/DM/vizMine', 'P/HST/GOODS/color', 'P/MATLAS/g'], target: '0 0', showProjectionControl: false, showSettingsControl: false, showLayersControl: true, showCooGrid: false, showFrame: false, showCooLocation: false});
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', lockNorthUp: true, fullScreen: true, showSettingsControl: true, fov: 360, survey: ['P/DM/vizMine', 'P/HST/GOODS/color', 'P/MATLAS/g'], target: '0 0', showProjectionControl: false, showSettingsControl: true, showLayersControl: true, showCooGrid: true, showFrame: false, showCooLocation: false});
});
</script>

View File

@@ -17,6 +17,8 @@
});
aladin.addCatalog(A.catalogFromVizieR("B/assocdata/obscore", "0 +0", 20, {onClick: 'showTable', hoverColor: 'yellow', limit: 1000}))
aladin.addCatalog(A.catalogFromSKAORucio("0 +0", 70, {onClick: 'showTable', hoverColor: 'yellow', limit: 1000}))
});
</script>
</body>

View File

@@ -13,9 +13,15 @@
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {target: 'Gamma Cas', fov: 10, cooFrame: 'icrs'});
var overlay = A.graphicOverlay({lineWidth: 2});
var overlay = A.graphicOverlay({color: 'green', lineWidth: 2});
aladin.addOverlay(overlay);
overlay.add(A.polyline([ [2.29452158, 59.14978110], [10.12683778, 56.53733116], [14.1772154, 60.7167403], [21.45396446, 60.23528403], [28.59885697, 63.67010079] ], {color: 'green'}));
overlay.add(A.polyline([ [2.29452158, 59.14978110], [10.12683778, 56.53733116], [14.1772154, 60.7167403], [21.45396446, 60.23528403], [28.59885697, 63.67010079] ], {
opacity: 0.7
}));
aladin.on('rotationChanged', (rot) => {
aladin.setRotation(rot)
})
});
</script>
</body>

View File

@@ -77,7 +77,7 @@
let bValues = [];
let i = 0;
for(var [r, g, b] of base.probe({type: 'line', x1: p.a.x, y1: p.a.y, x2: p.b.x, y2: p.b.y})) {
for(var [r, g, b] of base.probePixels({type: 'line', x1: p.a.x, y1: p.a.y, x2: p.b.x, y2: p.b.y})) {
xValues.push(i)
rValues.push(r)
gValues.push(g)

View File

@@ -10,7 +10,7 @@
<script type="text/javascript">
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, survey: 'https://alasky.cds.unistra.fr/DSS/DSSColor/', fov: 180, showContextMenu: true});
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, fov: 180, showContextMenu: true});
// manage URL parameters
const searchParams = new URL(document.location).searchParams;
if (searchParams.has('baseImageLayer')) {

View File

@@ -1,8 +1,8 @@
{
"homepage": "https://aladin.u-strasbg.fr/",
"homepage": "https://aladin.cds.unistra.fr/",
"name": "aladin-lite",
"type": "module",
"version": "3.7.0-beta",
"version": "3.7.3-beta",
"description": "An astronomical HiPS visualizer in the browser",
"author": "Thomas Boch and Matthieu Baumann",
"license": "GPL-3",
@@ -30,13 +30,17 @@
"HiPS"
],
"scripts": {
"wasm": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
"wasm:npm": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
"wasm:prod": "wasm-pack build ./src/core --target web --release --out-name core -- --features \"webgl2 minify_shaders\" && wasm-opt -Oz --strip-debug --strip-producers --dce -o src/core/pkg/core_bg.wasm src/core/pkg/core_bg.wasm",
"wasm:dev": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
"wasm:dbg": "wasm-pack build --dev ./src/core --target web --out-name core -- --features=webgl2,dbg",
"predeploy": "npm run build && rm -rf aladin-lite*.tgz && npm pack",
"predeploy": "npm run build:prod && rm -rf aladin-lite*.tgz && npm pack",
"deploy": "python3 deploy/deploy.py",
"build": "npm run wasm && vite build",
"build:npm": "npm run wasm:npm && vite build",
"build:prod": "npm run wasm:prod && vite build",
"build:dev": "npm run wasm:dev && vite build",
"build:dbg": "npm run wasm:dbg && vite build",
"dev": "npm run build && vite",
"dev": "npm run build:dev && vite",
"dev:dbg": "npm run build:dbg && vite",
"serve": "npm run dev",
"serve:dbg": "npm run dev:dbg",
@@ -45,14 +49,15 @@
"test:playwright": "npx playwright test",
"test:update-snapshots": "npx playwright test --update-snapshots",
"doc": "jsdoc -c jsdoc.json src/js src/js/shapes src/js/libs/astro && cp aladin-logo.png docs/ && cp jsdoc-custom-style.css docs/ && cp jsdoc-make-responsive.js docs/",
"doc:dev": "npm run doc && open docs/index.html"
"doc:dev": "npm run doc && open docs/index.html",
"analyze": "vite build --mode analyze"
},
"devDependencies": {
"@playwright/test": "^1.47.0",
"docdash": "^2.0.2",
"jsdoc": "^4.0.2",
"rollup-plugin-visualizer": "^6.0.3",
"vite": "^4.3.8",
"vite-plugin-glsl": "^1.1.2",
"vite-plugin-top-level-await": "^1.4.1",
"vite-plugin-wasm": "^3.2.2",
"vite-plugin-wasm-pack": "^0.1.12"

View File

@@ -3,7 +3,7 @@ name = "aladin-lite"
description = "Aladin Lite v3 introduces a new graphical engine written in Rust with the use of WebGL"
license = "BSD-3-Clause"
repository = "https://github.com/cds-astro/aladin-lite"
version = "3.7.0"
version = "3.7.3-beta"
authors = [ "baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr",]
edition = "2018"
@@ -18,21 +18,20 @@ futures = "0.3.12"
js-sys = "0.3.47"
wasm-bindgen-futures = "0.4.20"
cgmath = "*"
# url-lite = "0.1.0"
serde_json = "1.0.104"
serde-wasm-bindgen = "0.5"
enum_dispatch = "0.3.8"
wasm-bindgen = "=0.2.92"
#wasm-streams = "0.3.0"
async-channel = "1.8.0"
mapproj = "0.3.0"
fitsrs = "0.3.4"
colorgrad = "0.6.2"
fitsrs = "0.4.1"
[features]
webgl1 = [ "al-core/webgl1", "al-api/webgl1", "web-sys/WebGlRenderingContext", "web-sys/AngleInstancedArrays", "web-sys/ExtSRgb", "web-sys/OesTextureFloat",]
webgl2 = [ "al-core/webgl2", "al-api/webgl2", "web-sys/WebGl2RenderingContext", "web-sys/WebGlVertexArrayObject", "web-sys/ExtColorBufferFloat",]
dbg = [ "dep:console_error_panic_hook",]
minify_shaders = []
[dev-dependencies]
rand = "0.8"
@@ -84,7 +83,7 @@ overflow-checks = false
lto = true
panic = "abort"
incremental = false
codegen-units = 16
codegen-units = 1
rpath = false
[package.metadata.wasm-pack.profile.release]

View File

@@ -179,8 +179,9 @@ impl HiPSProperties {
#[wasm_bindgen]
#[serde(rename_all = "camelCase")]
pub enum ImageExt {
#[serde(alias = "fits", alias = "fits.fz")]
Fits,
#[serde(alias = "fits.fz")]
FitsFz,
Jpeg,
Png,
Webp,
@@ -199,6 +200,7 @@ pub enum DataproductType {
impl std::fmt::Display for ImageExt {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ImageExt::FitsFz => write!(f, "fits.fz"),
ImageExt::Fits => write!(f, "fits"),
ImageExt::Png => write!(f, "png"),
ImageExt::Jpeg => write!(f, "jpg"),

View File

@@ -7,9 +7,7 @@ edition = "2018"
[dependencies]
js-sys = "0.3.47"
cgmath = "*"
#jpeg-decoder = "0.3.0"
#png = "0.17.6"
fitsrs = "0.3.4"
fitsrs = "0.4.1"
al-api = { path = "../al-api" }
serde = { version = "^1.0.59", features = ["derive"] }
serde_json = "1.0"

View File

@@ -1,12 +1,13 @@
use crate::texture::format::TextureFormat;
use crate::texture::format::R8U;
use cgmath::Vector3;
use fitsrs::card::Value;
use fitsrs::hdu::data::bintable::data::BinaryTableData;
use fitsrs::hdu::data::bintable::tile_compressed::pixels::Pixels;
use fitsrs::hdu::header::extension::bintable::TileCompressedImage;
use fitsrs::hdu::header::Bitpix;
use fitsrs::hdu::header::Header;
use fitsrs::hdu::header::Xtension;
use fitsrs::WCS;
use fitsrs::{Fits, HDU};
use std::borrow::Cow;
use std::fmt::Debug;
use std::io::Cursor;
use std::ops::Range;
@@ -35,15 +36,7 @@ pub struct FitsImage<'a> {
// bytes offset where the data bytes are located inside the fits
pub data_byte_offset: Range<usize>,
// raw bytes of the data image (in Big-Endian)
pub raw_bytes: &'a [u8],
}
fn parse_keyword_as_number<X: Xtension + Debug>(header: &Header<X>, keyword: &str) -> Option<f32> {
match header.get(keyword) {
Some(Value::Integer { value, .. }) => Some(*value as f32),
Some(Value::Float { value, .. }) => Some(*value as f32),
_ => None,
}
pub raw_bytes: Cow<'a, [u8]>,
}
impl<'a> FitsImage<'a> {
@@ -57,22 +50,21 @@ impl<'a> FitsImage<'a> {
HDU::XImage(hdu) | HDU::Primary(hdu) => {
// Prefer getting the dimension directly from NAXIS1/NAXIS2 instead of from the WCS
// because it may not exist in all HDU images
let width = hdu.get_header().get_xtension().get_naxisn(1);
let height = hdu.get_header().get_xtension().get_naxisn(2);
if let (Some(&width), Some(&height)) = (width, height) {
let depth =
*hdu.get_header().get_xtension().get_naxisn(3).unwrap_or(&1) as u32;
let naxis = hdu.get_header().get_xtension().get_naxis();
if naxis.len() >= 2 {
let width = naxis[0];
let height = naxis[1];
let depth = if naxis.len() >= 3 { naxis[2] } else { 1 };
let header = hdu.get_header();
let bscale = parse_keyword_as_number(header, "BSCALE").unwrap_or(1.0);
let bzero = parse_keyword_as_number(header, "BZERO").unwrap_or(0.0);
let blank = parse_keyword_as_number(header, "BLANK");
let bscale = header.get_parsed::<f32>("BSCALE").unwrap_or(1.0);
let bzero = header.get_parsed::<f32>("BZERO").unwrap_or(0.0);
let blank = header.get_parsed::<f32>("BLANK").ok();
let trim1 = parse_keyword_as_number(header, "TRIM1").unwrap_or(0.0) as u32;
let trim2 = parse_keyword_as_number(header, "TRIM2").unwrap_or(0.0) as u32;
let trim3 = parse_keyword_as_number(header, "TRIM3").unwrap_or(0.0) as u32;
let trim1 = header.get_parsed::<u32>("TRIM1").unwrap_or(0);
let trim2 = header.get_parsed::<u32>("TRIM2").unwrap_or(0);
let trim3 = header.get_parsed::<u32>("TRIM3").unwrap_or(0);
let bitpix = hdu.get_header().get_xtension().get_bitpix();
@@ -80,7 +72,7 @@ impl<'a> FitsImage<'a> {
let len = hdu.get_data_unit_byte_size() as usize;
let data_byte_offset = off..(off + len);
let raw_bytes = &bytes[data_byte_offset.clone()];
let raw_bytes = Cow::Borrowed(&bytes[data_byte_offset.clone()]);
let wcs = hdu.wcs().ok();
@@ -90,7 +82,7 @@ impl<'a> FitsImage<'a> {
trim3,
width: width as u32,
height: height as u32,
depth,
depth: depth as u32,
bitpix,
bscale,
wcs,
@@ -101,6 +93,81 @@ impl<'a> FitsImage<'a> {
});
}
}
HDU::XBinaryTable(hdu) => {
let header = hdu.get_header();
let bin_table = header.get_xtension();
if let Some(TileCompressedImage {
z_bitpix: bitpix,
z_naxisn: naxis,
..
}) = &bin_table.get_z_image()
{
if naxis.len() >= 2 {
let width = naxis[0] as u32;
let height = naxis[1] as u32;
let depth = if naxis.len() >= 3 { naxis[2] as u32 } else { 1 };
let bscale = header.get_parsed::<f32>("BSCALE").unwrap_or(1.0);
let bzero = header.get_parsed::<f32>("BZERO").unwrap_or(0.0);
let blank = header.get_parsed::<f32>("BLANK").ok();
let trim1 = header.get_parsed::<u32>("TRIM1").unwrap_or(0);
let trim2 = header.get_parsed::<u32>("TRIM2").unwrap_or(0);
let trim3 = header.get_parsed::<u32>("TRIM3").unwrap_or(0);
let wcs = hdu.wcs().ok();
let off = hdu.get_data_unit_byte_offset() as usize;
let len = hdu.get_data_unit_byte_size() as usize;
let data_byte_offset = off..(off + len);
let mut bitpix = *bitpix;
let raw_bytes = match fits.get_data(&hdu) {
BinaryTableData::TileCompressed(Pixels::U8(pixels)) => {
Some(pixels.collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::I16(pixels)) => {
Some(pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::I32(pixels)) => {
Some(pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::F32(pixels)) => {
Some(pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::F64(pixels)) => {
bitpix = Bitpix::F32;
let raw_bytes =
pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>();
Some(raw_bytes)
}
_ => None,
};
if let Some(raw_bytes) = raw_bytes {
images.push(Self {
trim1,
trim2,
trim3,
width,
height,
depth,
bitpix,
bscale,
wcs,
bzero,
blank,
data_byte_offset,
raw_bytes: Cow::Owned(raw_bytes),
});
}
}
}
}
_ => (),
}
}
@@ -141,6 +208,7 @@ impl Image for FitsImage<'_> {
R8U::view(&new_bytes)
}
Bitpix::F64 => {
// convert to i64 first
let new_bytes: Vec<_> = self
.raw_bytes
.chunks_exact(8)
@@ -154,7 +222,7 @@ impl Image for FitsImage<'_> {
R8U::view(&new_bytes)
}
_ => R8U::view(self.raw_bytes),
_ => R8U::view(&self.raw_bytes),
}
};

View File

@@ -93,8 +93,6 @@ pub trait VertexAttribPointerType: std::marker::Sized {
}
}
use crate::webgl_ctx::WebGlRenderingCtx;
use js_sys::WebAssembly;
use wasm_bindgen::JsCast;
impl VertexAttribPointerType for u8 {
type ArrayBufferView = js_sys::Uint8Array;
@@ -308,7 +306,7 @@ impl VertexAttribPointerType for f32 {
type ArrayBufferView = Float32Array;
fn array_buffer_view<'a, B: BufferDataStorage<'a, Self>>(data: B) -> Self::ArrayBufferView {
let data = data.get_slice();
/*let data = data.get_slice();
//unsafe { Self::ArrayBufferView::view(&data) }
let memory_buffer = wasm_bindgen::memory()
.unchecked_ref::<WebAssembly::Memory>()
@@ -316,7 +314,9 @@ impl VertexAttribPointerType for f32 {
let len = data.len();
let ptr = data.as_ptr() as u32 / 4;
Float32Array::new(&memory_buffer).subarray(ptr, ptr + len as u32)
Float32Array::new(&memory_buffer).subarray(ptr, ptr + len as u32)*/
let data = data.get_slice();
unsafe { Self::ArrayBufferView::view(data) }
}
fn buffer_sub_data_with_i32_and_array_buffer_view<'a, B: BufferDataStorage<'a, Self>>(
@@ -462,6 +462,30 @@ impl ArrayBuffer {
);
}
}
/*pub fn update_from_js_array<'a, T: VertexAttribPointerType>(
&mut self,
usage: u32,
data: T::ArrayBufferView,
) {
self.bind();
if self.len >= data.len() {
T::buffer_sub_data_with_i32_and_array_buffer_view(
&self.gl,
data,
WebGlRenderingCtx::ARRAY_BUFFER,
);
} else {
self.len = data.len();
T::buffer_data_with_array_buffer_view(
&self.gl,
data,
WebGlRenderingCtx::ARRAY_BUFFER,
usage,
);
}
}*/
}
impl VertexBufferObject for ArrayBuffer {

View File

@@ -132,6 +132,20 @@ pub mod vao {
self
}
/*pub fn update_from_js_array<T: VertexAttribPointerType>(
&mut self,
attr: &'static str,
usage: u32,
js_array: T::ArrayBufferView,
) -> &mut Self {
self.vao
.array_buffer
.get_mut(attr)
.unwrap_abort()
.update_from_js_array::<T>(usage, js_array);
self
}*/
pub fn update_element_array<T: VertexAttribPointerType, B: BufferDataStorage<'a, T>>(
&mut self,
usage: u32,

View File

@@ -91,7 +91,7 @@ impl Texture2DArray {
// Attach the texture as the first color attachment
self.gl.framebuffer_texture_layer(
WebGlRenderingCtx::READ_FRAMEBUFFER,
WebGlRenderingCtx::FRAMEBUFFER,
WebGlRenderingCtx::COLOR_ATTACHMENT0,
self.texture.as_ref(),
0,

View File

@@ -203,5 +203,3 @@ impl PixelType {
}
}
}
pub const NUM_CHANNELS: usize = 6;

View File

@@ -311,7 +311,7 @@ impl Texture2D {
// Attach the texture as the first color attachment
//self.attach_to_framebuffer();
self.gl.framebuffer_texture_2d(
WebGlRenderingCtx::READ_FRAMEBUFFER,
WebGlRenderingCtx::FRAMEBUFFER,
WebGlRenderingCtx::COLOR_ATTACHMENT0,
WebGlRenderingCtx::TEXTURE_2D,
self.texture.as_ref(),

View File

@@ -21,32 +21,6 @@ pub trait Pixel:
fn read_pixel(gl: &WebGlContext, x: i32, y: i32) -> Result<Self, JsValue>;
}
impl Pixel for [f32; 1] {
type Item = f32;
type Container = ArrayF32;
const BLACK: Self = [f32::NAN];
fn read_pixel(gl: &WebGlContext, x: i32, y: i32) -> Result<Self, JsValue> {
let p = js_sys::Uint8Array::new_with_length(4);
gl.read_pixels_with_opt_array_buffer_view(
x,
y,
1,
1,
WebGlRenderingCtx::RGBA,
WebGlRenderingCtx::UNSIGNED_BYTE,
Some(&p),
)?;
Ok([f32::from_le_bytes([
p.at(0).unwrap(),
p.at(1).unwrap(),
p.at(2).unwrap(),
p.at(3).unwrap(),
])])
}
}
impl Pixel for [u8; 4] {
type Item = u8;
type Container = ArrayU8;
@@ -74,13 +48,13 @@ impl Pixel for [u8; 3] {
const BLACK: Self = [0, 0, 0];
fn read_pixel(gl: &WebGlContext, x: i32, y: i32) -> Result<Self, JsValue> {
let pixels = js_sys::Uint8Array::new_with_length(3);
let pixels = js_sys::Uint8Array::new_with_length(4);
gl.read_pixels_with_opt_array_buffer_view(
x,
y,
1,
1,
WebGlRenderingCtx::RGB,
WebGlRenderingCtx::RGBA,
WebGlRenderingCtx::UNSIGNED_BYTE,
Some(&pixels),
)?;
@@ -147,7 +121,7 @@ impl Pixel for [i16; 1] {
Some(&p),
)?;
Ok([i16::from_le_bytes([p.at(0).unwrap(), p.at(1).unwrap()])])
Ok([i16::from_le_bytes([p.at(1).unwrap(), p.at(0).unwrap()])])
}
}
@@ -169,10 +143,36 @@ impl Pixel for [i32; 1] {
)?;
Ok([i32::from_le_bytes([
p.at(0).unwrap(),
p.at(1).unwrap(),
p.at(2).unwrap(),
p.at(3).unwrap(),
p.at(2).unwrap(),
p.at(1).unwrap(),
p.at(0).unwrap(),
])])
}
}
impl Pixel for [f32; 1] {
type Item = f32;
type Container = ArrayF32;
const BLACK: Self = [f32::NAN];
fn read_pixel(gl: &WebGlContext, x: i32, y: i32) -> Result<Self, JsValue> {
let p = js_sys::Uint8Array::new_with_length(4);
gl.read_pixels_with_opt_array_buffer_view(
x,
y,
1,
1,
WebGlRenderingCtx::RGBA,
WebGlRenderingCtx::UNSIGNED_BYTE,
Some(&p),
)?;
Ok([f32::from_le_bytes([
p.at(3).unwrap(),
p.at(2).unwrap(),
p.at(1).unwrap(),
p.at(0).unwrap(),
])])
}
}

View File

@@ -49,12 +49,32 @@ impl WebGlContext {
#[cfg(feature = "webgl2")]
{
/*if let Ok(r) =
get_extension::<web_sys::ExtColorBufferFloat>(&gl, "EXT_color_buffer_float")
{
let _ = r;
}*/
let ctx = WebGlContext { inner: gl };
Ok(ctx)
}
}
}
fn _get_extension<T>(context: &WebGlRenderingCtx, name: &str) -> Result<T, JsValue>
where
T: wasm_bindgen::JsCast,
{
// `unchecked_into` is used here because WebGL extensions aren't actually JS classes
// these objects are duck-type representations of the actual Rust classes
// https://github.com/rustwasm/wasm-bindgen/pull/1449
context
.get_extension(name)
.ok()
.and_then(|maybe_ext| maybe_ext.map(|ext| ext.unchecked_into::<T>()))
.ok_or_else(|| JsValue::from_str("Failed to load ext"))
}
use std::ops::Deref;
impl Deref for WebGlContext {
type Target = WebGlRenderingCtx;

View File

@@ -3,6 +3,8 @@ use walkdir::WalkDir;
extern crate walkdir;
use std::io::BufRead;
use std::process::Command;
// All my shaders reside in the 'src/shaders' directory
fn generate_shaders() -> std::result::Result<(), Box<dyn Error>> {
println!("generate shaders");
@@ -26,9 +28,48 @@ fn generate_shaders() -> std::result::Result<(), Box<dyn Error>> {
.into_owned()
.replace("/", "_")
.replace("\\", "_");
//let out_name = format!("{}/{}", OUT_PATH, out_file_name);
let src = read_shader(path)?;
let mut src = read_shader(path)?;
if std::env::var("CARGO_FEATURE_MINIFY_SHADERS").is_ok() {
println!("Feature `minify_shaders` enabled: running shader minifier");
// save to a minified path
let mut tmp_path = PathBuf::from(path);
let mut min_path = PathBuf::from(path);
let stem = path.file_stem().unwrap();
// Build new filename: stem + ".min" + extension
let tmp_file_name =
format!("{}.{}.tmp", stem.to_string_lossy(), ext.to_string_lossy());
tmp_path.set_file_name(tmp_file_name);
let min_file_name =
format!("{}.{}.min", stem.to_string_lossy(), ext.to_string_lossy());
min_path.set_file_name(min_file_name);
fs::write(tmp_path.clone(), &src)?;
Command::new("mono")
.args([
"/Users/matthieubaumann/Downloads/shader_minifier.exe",
"--format",
"text",
"--aggressive-inlining",
"--preserve-externals",
"--move-declarations",
"-o",
min_path.as_os_str().to_str().expect("Invalid UTF-8!"),
tmp_path.as_os_str().to_str().expect("Invalid UTF-8!"),
])
.status()
.expect("Failed to run shader_minifier");
src = read_shader(min_path)?;
} else {
println!("Feature `minify_shaders` not enabled: skipping");
}
shaders.insert(out_file_name, src);
println!("cargo:rerun-if-changed=src/shaders/{file_name}");

View File

@@ -87,6 +87,7 @@ pub struct App {
time_start_dragging: Time,
time_mouse_high_vel: Time,
dragging: bool,
vel_history: Vec<f32>,
prev_cam_position: Vector3<f64>,
//prev_center: Vector3<f64>,
@@ -115,7 +116,7 @@ pub struct App {
//callback_position_changed: js_sys::Function,
}
use cgmath::{Vector2, Vector3};
use cgmath::{Vector2, Vector3, Zero};
use crate::math::projection::*;
pub const BLENDING_ANIM_DURATION: DeltaTime = DeltaTime::from_millis(200.0); // in ms
@@ -208,6 +209,7 @@ impl App {
let browser_features_support = BrowserFeaturesSupport::new();
let vel_history = vec![];
Ok(App {
gl,
//ui,
@@ -244,6 +246,7 @@ impl App {
time_start_dragging,
time_mouse_high_vel,
dragging,
vel_history,
prev_cam_position,
out_of_fov,
@@ -264,12 +267,21 @@ impl App {
})
}
fn _update_hips_location(&mut self) {
let camera = &self.camera;
for hips in self.layers.get_mut_hipses() {
if let HiPS::D3(hips) = hips {
hips.set_cursor_location(camera);
}
}
}
fn look_for_new_tiles(&mut self) -> Result<(), JsValue> {
// Move the views of the different active hipss
self.tile_fetcher.clear();
// Loop over the hipss
for hips in self.layers.get_mut_hipses() {
if self.camera.get_tile_depth() == 0 {
/*if self.camera.get_tile_depth() == 0 {
match hips {
HiPS::D2(h) => {
let query = query::Allsky::new(h.get_config(), None);
@@ -281,7 +293,7 @@ impl App {
// no Allsky generated for HiPS3D
HiPS::D3(_) => (),
}
}
}*/
hips.look_for_new_tiles(
&mut self.tile_fetcher,
@@ -492,7 +504,7 @@ impl App {
self.inertia.is_some()
}
pub(crate) fn update(&mut self, dt: DeltaTime) -> Result<bool, JsValue> {
pub(crate) fn update(&mut self, dt: f64) -> Result<bool, JsValue> {
// a timer stopping the frame if it takes too long
// useful for garanting a framerate
let rendering_timer = Time::now();
@@ -505,7 +517,7 @@ impl App {
// The threshold stopping criteria must be dependant
// of the zoom level, in this case the initial angular distance
// speed
let thresh_speed = inertia.get_start_ampl() * 1e-3;
let thresh_speed = inertia.get_start_ampl() * 1e-4;
let cur_speed = inertia.get_cur_speed();
if cur_speed < thresh_speed {
@@ -513,8 +525,6 @@ impl App {
}
}
self.draw()?;
// Check for async retrieval
if let Ok(img) = self.img_recv.try_recv() {
let params = img.get_params();
@@ -543,9 +553,10 @@ impl App {
/*let is_there_new_available_tiles = self
.downloader
.get_resolved_tiles(/*&available_tiles, */&mut self.hipss);*/
//self.tile_fetcher.clear();
if self.request_for_new_tiles
//&& Time::now() - self.last_time_request_for_new_tiles > DeltaTime::from(200.0)
&& Time::now() - self.last_time_request_for_new_tiles > DeltaTime::from(500.0)
{
self.look_for_new_tiles()?;
@@ -554,25 +565,29 @@ impl App {
}
// Tiles are fetched if:
let fetch_tiles =
// * the user is not panning the view
// * or the user is but did not move for at least 100ms
(Time::now() - self.camera.get_time_of_last_move() >= DeltaTime(100.0) || !self.dragging) &&
// * no inertia action is in progress
self.inertia.is_none() &&
// * the user is not zooming
!self.camera.has_zoomed();
//let fetch_tiles =
// * the user is not panning the view
// * or the user is but did not move for at least 100ms
//(Time::now() - self.camera.get_time_of_last_move() >= DeltaTime(100.0) || !self.dragging) &&
// * no inertia action is in progress
//self.inertia.is_none() &&
// * the user is not zooming
// !self.camera.has_zoomed();
if fetch_tiles {
self.tile_fetcher.notify(self.downloader.clone(), None);
}
//if fetch_tiles {
self.tile_fetcher.notify(self.downloader.clone(), None);
//}
}
let rscs_received = self.downloader.borrow_mut().get_received_resources();
let mut tile_copied = false;
const MAX_FRAME_TIME: DeltaTime = DeltaTime::from_millis(1000.0 / 25.0);
const MAX_FRAME_TIME: DeltaTime = DeltaTime::from_millis(1000.0 / 40.0);
// - there is at least one tile in its blending phase
let blending_anim_occuring =
(Time::now() - self.time_start_blending) < BLENDING_ANIM_DURATION;
for rsc in rscs_received {
if Time::now() - rendering_timer >= MAX_FRAME_TIME {
@@ -922,16 +937,14 @@ impl App {
}
}
// - there is at least one tile in its blending phase
let blending_anim_occuring =
(Time::now() - self.time_start_blending) < BLENDING_ANIM_DURATION;
self.rendering = blending_anim_occuring
| has_camera_moved
| self.camera.has_zoomed()
| self.request_redraw
| self.inertia.is_some();
self.draw()?;
// Reset the flags about the user action
self.camera.reset();
@@ -1028,64 +1041,63 @@ impl App {
self.layers.reset_frame();*/
//let scene_redraw = self.rendering | force_render;
let scene_redraw = true;
//let mut ui = self.ui.lock();
//let ui_redraw = ui.redraw_needed();
//if scene_redraw || ui_redraw {
if scene_redraw {
self.request_redraw = false;
let shaders = &mut self.shaders;
self.request_redraw = false;
let gl = self.gl.clone();
let shaders = &mut self.shaders;
let camera = &mut self.camera;
let gl = self.gl.clone();
let grid = &mut self.grid;
let moc = &mut self.moc;
let projection = &self.projection;
let camera = &mut self.camera;
let layers = &mut self.layers;
//let catalogs = &self.manager;
let colormaps = &self.colormaps;
//let fbo_view = &self._fbo_view;
//let final_rendering_pass = &self._final_rendering_pass;
let grid = &mut self.grid;
let moc = &mut self.moc;
let projection = &self.projection;
//fbo_view.draw_onto(
// move || {
// Render the scene
// Clear all the screen first (only the region set by the scissor)
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
let layers = &mut self.layers;
//let catalogs = &self.manager;
let colormaps = &self.colormaps;
//let fbo_view = &self._fbo_view;
//let final_rendering_pass = &self._final_rendering_pass;
// set the blending options
layers.draw(camera, shaders, colormaps, projection)?;
//fbo_view.draw_onto(
// move || {
// Render the scene
// Clear all the screen first (only the region set by the scissor)
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
// Draw the catalog
//let fbo_view = &self.fbo_view;
//catalogs.draw(&gl, shaders, camera, colormaps, fbo_view)?;
//catalogs.draw(&gl, shaders, camera, colormaps, None, self.projection)?;
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
moc.draw(camera, projection, shaders)?;
// set the blending options
layers.draw(camera, shaders, colormaps, projection)?;
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
grid.draw(camera, projection, shaders)?;
// Ok(())
// },
// None,
//)?;
// Draw the catalog
//let fbo_view = &self.fbo_view;
//catalogs.draw(&gl, shaders, camera, colormaps, fbo_view)?;
//catalogs.draw(&gl, shaders, camera, colormaps, None, self.projection)?;
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
moc.draw(camera, projection, shaders)?;
//final_rendering_pass.draw_on_screen(fbo_view, &mut self.shaders)?;
}
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
grid.draw(camera, projection, shaders)?;
// Ok(())
// },
// None,
//)?;
//final_rendering_pass.draw_on_screen(fbo_view, &mut self.shaders)?;
Ok(())
}
@@ -1103,10 +1115,6 @@ impl App {
Ok(())
}
pub(crate) fn rename_layer(&mut self, layer: &str, new_layer: &str) -> Result<(), JsValue> {
self.layers.rename_layer(layer, new_layer)
}
pub(crate) fn swap_layers(
&mut self,
first_layer: &str,
@@ -1220,7 +1228,7 @@ impl App {
&gl,
wcs,
bitpix,
raw_bytes,
raw_bytes.as_ref(),
bscale,
bzero,
blank,
@@ -1529,6 +1537,8 @@ impl App {
// And stop the current inertia as well if there is one
self.inertia = None;
self._update_hips_location();
}
pub(crate) fn move_mouse(&mut self, s1x: f32, s1y: f32, s2x: f32, s2y: f32) {
@@ -1538,7 +1548,16 @@ impl App {
let dx = crate::math::vector::dist2(&from_mouse_pos, &to_mouse_pos).sqrt();
self.dist_dragging += dx;
//let now = Time::now();
//let dragging_duration = (now - self.time_start_dragging).as_secs();
//let dragging_vel = self.dist_dragging / dragging_duration;
// 1. Use smoothed velocity instead of instantaneous velocity
let dv = dx / (Time::now() - self.camera.get_time_of_last_move()).as_secs();
self.vel_history.push(dv);
if self.vel_history.len() > 5 {
self.vel_history.remove(0);
}
if dv > 10000.0 {
self.time_mouse_high_vel = Time::now();
@@ -1587,15 +1606,18 @@ impl App {
}
let now = Time::now();
let dragging_duration = (now - self.time_start_dragging).as_secs();
let dragging_vel = self.dist_dragging / dragging_duration;
let avg_vel = self.vel_history.iter().copied().sum::<f32>() / self.vel_history.len() as f32;
// Detect if there has been a recent acceleration
// It is also possible that the dragging time is too short and if it is the case, trigger the inertia
let recent_acceleration = (Time::now() - self.time_mouse_high_vel).as_secs() < 0.1
|| (Time::now() - self.time_start_dragging).as_secs() < 0.1;
// 2. Clamp minimum + maximum velocities
let min_vel = 1000.0; // tweak
if dragging_vel < 2000.0 && !recent_acceleration {
// 3. Better condition for “recent acceleration
let t_since_drag = (now - self.time_start_dragging).as_secs();
let t_since_accel = (now - self.time_mouse_high_vel).as_secs();
let inertia_trigger =
avg_vel > min_vel || ((t_since_drag < 0.15) || (t_since_accel < 0.15));
if !inertia_trigger {
return;
}
@@ -1605,10 +1627,8 @@ impl App {
let center = self.camera.get_center();
let axis = self.prev_cam_position.cross(*center).normalize();
//let delta_time = ((now - time_of_last_move).0 as f64).max(1.0);
let delta_angle = math::vector::angle3(&self.prev_cam_position, center).to_radians();
let ampl = delta_angle * (dragging_vel as f64) * 5e-3;
//let ampl = (dragging_vel * 0.01) as f64;
let ampl = (delta_angle * avg_vel as f64) * 5e-3;
self.inertia = Some(Inertia::new(ampl.to_radians(), axis, self.north_up))
}
@@ -1718,7 +1738,6 @@ impl App {
}
} else {
/* 1. Rotate by computing the angle between the last and current position */
let d = math::vector::angle3(&prev_pos, &cur_pos);
let axis = prev_pos.cross(cur_pos).normalize();
@@ -1728,9 +1747,39 @@ impl App {
self.prev_cam_position = prev_cam_position;
self.request_for_new_tiles = true;
self._update_hips_location();
}
} else {
self.out_of_fov = true;
// approx move
let origin2next = Vector2::new(s2x - s1x, s2y - s1y);
if origin2next != Vector2::zero() {
let prev_pos = self.camera.get_center();
let prev_cam_position = self.get_center().vector();
let center_screen = self
.projection
.model_to_screen_space(&prev_cam_position, &self.camera)
.unwrap();
let next_s = origin2next + center_screen;
if let Some(cur_pos) = self.projection.screen_to_model_space(&next_s, &self.camera)
{
let d = math::vector::angle3(prev_pos, &cur_pos);
let axis = prev_pos.cross(cur_pos).normalize();
self.camera
.apply_axis_rotation(&(-axis), d, &self.projection);
self.prev_cam_position = prev_cam_position;
self.request_for_new_tiles = true;
self._update_hips_location();
}
}
}
}
@@ -1758,6 +1807,8 @@ impl App {
pub(crate) fn set_zoom_factor(&mut self, zoom_factor: f64) {
self.camera.set_zoom_factor(zoom_factor, &self.projection);
self._update_hips_location();
self.request_for_new_tiles = true;
self.request_redraw = true;
}

View File

@@ -315,11 +315,7 @@ impl CameraViewPort {
}
pub fn compute_ndc_to_clip_factor(&mut self, proj: &ProjectionType) {
self.ndc_to_clip = if self.height < self.width {
Vector2::new(1.0, (self.height as f64) / (self.width as f64))
} else {
Vector2::new((self.width as f64) / (self.height as f64), 1.0)
};
self.ndc_to_clip = Vector2::new(1.0, (self.height as f64) / (self.width as f64));
let bounds_size_ratio = proj.bounds_size_ratio();
self.ndc_to_clip.y *= bounds_size_ratio;
@@ -570,42 +566,48 @@ impl CameraViewPort {
}
fn compute_texture_depth(&mut self) {
/*// Compute a depth from a number of pixels on screen
let width = self.width;
let aperture = self.aperture.0 as f32;
// Compute a depth from a number of pixels on screen
/*let width = self.width;
let aperture = self.aperture as f32;
let angle_per_pixel = aperture / width;
let angle_per_pixel = aperture / width;
let two_power_two_times_depth_pixel =
std::f32::consts::PI / (3.0 * angle_per_pixel * angle_per_pixel);
let depth_pixel = (two_power_two_times_depth_pixel.log2() / 2.0).floor() as u32;
let two_power_two_times_depth_pixel =
std::f32::consts::PI / (3.0 * angle_per_pixel * angle_per_pixel);
let depth_pixel = (two_power_two_times_depth_pixel.log2() / 2.0).ceil() as u32;
//let survey_max_depth = conf.get_max_depth();
// The depth of the texture
// A texture of 512x512 pixels will have a depth of 9
const DEPTH_OFFSET_TEXTURE: u32 = 9;
// The depth of the texture corresponds to the depth of a pixel
// minus the offset depth of the texture
self.texture_depth = if DEPTH_OFFSET_TEXTURE > depth_pixel {
0_u8
} else {
(depth_pixel - DEPTH_OFFSET_TEXTURE) as u8
};*/
//let survey_max_depth = conf.get_max_depth();
// The depth of the texture
// A texture of 512x512 pixels will have a depth of 9
const DEPTH_OFFSET_TEXTURE: u32 = 9;
// The depth of the texture corresponds to the depth of a pixel
// minus the offset depth of the texture
self.texture_depth = if DEPTH_OFFSET_TEXTURE > depth_pixel {
0_u8
} else {
(depth_pixel - DEPTH_OFFSET_TEXTURE) as u8
};
*/
let w_screen_device_px = self.width as f64 / (self.dpi as f64);
//let depth_pixel = 29_usize;
let w_screen_px = self.width as f64;
let smallest_cell_size_px = self.dpi as f64;
let mut depth_pixel = 29_usize;
let pixel_angle_rad = self.get_aperture() / w_screen_device_px;
let hpx_cell_size_rad = (smallest_cell_size_px / w_screen_px) * self.get_aperture();
while depth_pixel > 0 {
if crate::healpix::utils::MEAN_HPX_CELL_RES[depth_pixel] > hpx_cell_size_rad {
break;
// Find the smallest depth such that MEAN_HPX_CELL_RES[depth] > pixel_angle_rad
let depth_pixel = match crate::healpix::utils::MEAN_HPX_CELL_RES.binary_search_by(|&res| {
if res < pixel_angle_rad {
std::cmp::Ordering::Greater
} else if res > pixel_angle_rad {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Equal
}
}) {
Ok(idx) => idx, // exact match
Err(idx) => idx,
};
depth_pixel -= 1;
}
depth_pixel += 1;
//al_core::log(&format!("{:?}", depth_pixel));
const DEPTH_OFFSET_TEXTURE: usize = 9;
self.texture_depth = if DEPTH_OFFSET_TEXTURE > depth_pixel {
0_u8

View File

@@ -96,11 +96,7 @@ impl Downloader {
}
pub fn delay(&mut self, r: RequestType) {
match r {
RequestType::Tile(tile) => {
self.cache.insert(tile.id.clone(), RequestType::Tile(tile));
}
_ => unimplemented!(),
}
let id = r.id().to_owned();
self.cache.insert(id, r);
}
}

View File

@@ -147,7 +147,7 @@ impl From<query::Allsky> for AllskyRequest {
let FitsImage {
raw_bytes, bitpix, ..
} = FitsImage::from_raw_bytes(raw_bytes.as_slice())?[0];
} = &FitsImage::from_raw_bytes(raw_bytes.as_slice())?[0];
match bitpix {
Bitpix::U8 => {
Ok(handle_allsky_fits(raw_bytes, tile_size, allsky_tile_size)?
@@ -213,7 +213,6 @@ impl From<query::Allsky> for AllskyRequest {
Self {
id,
hips_cdid,
//depth_tile,
url,
request,
channel: slice,

View File

@@ -133,8 +133,9 @@ async fn query_html_image(
// Set the CORS and credentials options for the image
let cors_value = match credentials {
RequestCredentials::Include => Some("use-credentials"),
RequestCredentials::SameOrigin => Some("anonymous"),
_ => Some(""),
RequestCredentials::Omit => Some("anonymous"),
RequestCredentials::SameOrigin => Some(""),
_ => None,
};
let promise = js_sys::Promise::new(

View File

@@ -99,10 +99,6 @@ impl FreqSpaceMoc {
let f_hash_0 = f_hash << (Frequency::<u64>::MAX_DEPTH - f_depth);
let f_hash_1 = (f_hash + 1) << (Frequency::<u64>::MAX_DEPTH - f_depth);
//let f0 = Frequency::<u64>::hash2freq(5171582628058365952);
//let f1 = Frequency::<u64>::hash2freq(5171590187200806912);
//al_core::log(&format!("F1: {f0}"));
let hpx_ranges_2d = HpxRanges2D::create_from_freq_ranges_positions(
vec![f_hash_0..f_hash_1; 1],
vec![hpx.idx()],

View File

@@ -3,7 +3,6 @@ use cgmath::Vector3;
use crate::camera::CameraViewPort;
use crate::math::angle::ToAngle;
use crate::math::projection::ProjectionType;
use crate::time::{DeltaTime, Time};
/// State for inertia
pub struct Inertia {
// Initial angular distance
@@ -12,21 +11,20 @@ pub struct Inertia {
// Vector of rotation
axis: Vector3<f64>,
// The time when the inertia begins
time_start: Time,
north_up: bool,
}
impl Inertia {
pub fn new(ampl: f64, axis: Vector3<f64>, north_up: bool) -> Self {
Inertia {
time_start: Time::now(),
ampl,
speed: ampl,
speed: (ampl * 0.5).min(0.1),
axis,
north_up,
}
}
/*
pub fn apply(&mut self, camera: &mut CameraViewPort, proj: &ProjectionType, _dt: DeltaTime) {
let t = ((Time::now() - self.time_start).as_millis() / 1000.0) as f64;
// Undamped angular frequency of the oscillator
@@ -46,6 +44,27 @@ impl Inertia {
let fov = start_fov * (1_f32 - alpha) + goal_fov * alpha;*/
camera.apply_axis_rotation(&self.axis, self.speed.to_angle(), proj);
if self.north_up {
camera.set_position_angle(0.0.to_angle(), proj);
}
}*/
pub fn apply(&mut self, camera: &mut CameraViewPort, proj: &ProjectionType, dt: f64) {
// Initial angular velocity
//let v0 = self.ampl * 0.5;
// Friction coefficient (tweak this)
const DAMPING_FACTOR: f64 = 5e-3;
self.speed *= (-DAMPING_FACTOR * dt).exp();
let delta_angle = self.speed * dt;
// Exponential decay of angular velocity
// self.speed = (v0 * (-damping * t).exp()).min(3.0);
//camera.apply_axis_rotation(&self.axis, self.speed.to_angle(), proj);
camera.apply_axis_rotation(&self.axis, delta_angle.to_angle(), proj);
if self.north_up {
camera.set_position_angle(0.0.to_angle(), proj);
}

View File

@@ -17,6 +17,7 @@
//extern crate num;
//extern crate num_traits;
//use crate::time::Time;
#[cfg(feature = "dbg")]
use std::panic;
@@ -115,7 +116,6 @@ mod time;
use crate::{
camera::CameraViewPort, healpix::moc::SpaceMoc, math::lonlat::LonLatT, shader::ShaderManager,
time::DeltaTime,
};
use al_api::color::{Color, ColorRGBA};
@@ -136,10 +136,6 @@ use math::angle::ArcDeg;
pub struct WebClient {
// The app
app: App,
// The time between the previous and the current
// frame
dt: DeltaTime,
}
use al_api::hips::ImageMetadata;
@@ -166,9 +162,7 @@ impl WebClient {
let app = App::new(&gl, aladin_div, shaders, resources)?;
let dt = DeltaTime::zero();
let webclient = WebClient { app, dt };
let webclient = WebClient { app };
Ok(webclient)
}
@@ -193,15 +187,15 @@ impl WebClient {
///
/// # Return
/// Whether the view is moving or not
pub fn update(&mut self, dt: f32) -> Result<bool, JsValue> {
pub fn update(&mut self, dt: f64) -> Result<bool, JsValue> {
// dt refers to the time taking (in ms) rendering the previous frame
self.dt = DeltaTime::from_millis(dt);
//self.dt = DeltaTime::from_millis(dt as f32);
// Update the application and get back the
// world coordinates of the center of projection in (ra, dec)
self.app.update(
// Time of the previous frame rendering
self.dt,
dt,
)
}
@@ -378,12 +372,6 @@ impl WebClient {
Ok(())
}
#[wasm_bindgen(js_name = renameLayer)]
pub fn rename_layer(&mut self, layer: String, new_layer: String) -> Result<(), JsValue> {
// Deserialize the hips objects that compose the hips
self.app.rename_layer(&layer, &new_layer)
}
#[wasm_bindgen(js_name = swapLayers)]
pub fn swap_layers(
&mut self,

View File

@@ -111,27 +111,50 @@ where
// Define a rotation from an axis and a angle
pub fn from_axis_angle(axis: &Vector3<S>, angle: Angle<S>) -> Rotation<S> {
let angle: Rad<S> = angle.into();
let mat = Matrix3::from_axis_angle(axis.normalize(), angle);
(&mat).into()
let half = angle.0 * S::from(0.5).unwrap();
let (s, c) = half.sin_cos();
let axis = axis.normalize();
let q = Quaternion::new(c, axis.x * s, axis.y * s, axis.z * s);
Rotation(q)
}
// Define a rotation from a normalized vector
pub fn from_sky_position(pos: &Vector3<S>) -> Rotation<S> {
let (lon, lat) = math::lonlat::xyz_to_radec(pos);
let qy = Self::from_axis_angle(&Vector3::unit_y(), lon);
let qx = Self::from_axis_angle(&Vector3::unit_x(), -lat);
qy * qx
}
/*pub fn from_sky_position(pos: &Vector3<S>) -> Rotation<S> {
let (lon, lat) = math::lonlat::xyz_to_radec(pos);
let rot_y = Matrix3::from_angle_y(lon);
let rot_x = Matrix3::from_angle_x(-lat);
let mat = rot_y * rot_x;
(&(mat)).into()
}
}*/
// Apply a rotation to a position
pub fn rotate(&self, pos_world_space: &Vector3<S>) -> Vector3<S> {
let w2m: &Matrix3<S> = &self.into();
pub fn rotate(&self, v: &Vector3<S>) -> Vector3<S> {
/*let w2m: &Matrix3<S> = &self.into();
w2m * v*/
let qvec = self.0.v; // vector part of the quaternion
w2m * pos_world_space
// uv = qvec × v
let uv = qvec.cross(*v);
// uuv = qvec × uv
let uuv = qvec.cross(uv);
// v' = v + 2 * (uv * q.w + uuv)
*v + ((uv * self.0.s) + uuv) * (S::from(2.0).unwrap())
}
pub fn inv_rotate(&self, pos_model_space: &Vector3<S>) -> Vector3<S> {
let w2m: &Matrix3<S> = &self.into();
let m2w = w2m.transpose();

View File

@@ -77,7 +77,7 @@ impl HiPSConfig {
}
let format = match img_ext {
ImageExt::Fits => {
ImageExt::Fits | ImageExt::FitsFz => {
// Check the bitpix to determine the internal format of the tiles
if let Some(bitpix) = bitpix {
let fmt = (match bitpix {
@@ -176,7 +176,7 @@ impl HiPSConfig {
pub fn set_image_ext(&mut self, ext: ImageExt) -> Result<(), JsValue> {
let format = match ext {
ImageExt::Fits => {
ImageExt::Fits | ImageExt::FitsFz => {
// Check the bitpix to determine the internal format of the tiles
if let Some(bitpix) = self.bitpix {
let fmt = (match bitpix {

View File

@@ -77,34 +77,37 @@ fn create_hpx_texture_storage(
WebGlRenderingCtx::TEXTURE_WRAP_T,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
// Prevents r-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_R,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
];
match channel {
PixelType::RGBA8U => Texture2DArray::create_empty::<RGBA8U>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
num_tiles, tex_params,
),
PixelType::RGB8U => Texture2DArray::create_empty::<RGB8U>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
num_tiles, tex_params,
),
PixelType::RGBA8U => {
Texture2DArray::create_empty::<RGBA8U>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
num_tiles, tex_params,
)
}
PixelType::RGB8U => {
Texture2DArray::create_empty::<RGB8U>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
num_tiles, tex_params,
)
}
PixelType::R32F => Texture2DArray::create_empty::<R32F>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
num_tiles, tex_params,
),
PixelType::R8U => Texture2DArray::create_empty::<R8U>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
num_tiles, tex_params,
),
PixelType::R16I => Texture2DArray::create_empty::<R16I>(
gl, tile_size, tile_size,
// 256 is a consensus for targetting the maximum GPU architectures. We create a 128 slices to optimize performance
@@ -211,9 +214,9 @@ impl HiPS2DBuffer {
)?;
self.num_root_textures_available += 1;
if self.num_root_textures_available == 12 {
/*if self.num_root_textures_available == 12 {
self.allsky_pixels.generate_mipmap()
}
}*/
}
self.available_tiles_during_frame = true;
@@ -285,7 +288,7 @@ impl HiPS2DBuffer {
.tile_pixels
.read_pixel(pos_tex.x, pos_tex.y, pos_tex.z)?,
_ => {
let uvy = 1.0 - (pos_tex.y as f32 / tile_size);
let uvy = 1.0 - (dx as f32);
pos_tex.y = (uvy * tile_size) as i32;
let f64_v = self
@@ -357,6 +360,7 @@ impl HpxTileBuffer for HiPS2DBuffer {
HpxTex::new(&HEALPixCell(0, 10), 10, now),
HpxTex::new(&HEALPixCell(0, 11), 11, now),
];
let channel = config.get_format().get_pixel_format();
let tile_size = config.get_tile_size();
let tile_pixels = create_hpx_texture_storage(gl, channel, 128, tile_size)?;
@@ -390,6 +394,7 @@ impl HpxTileBuffer for HiPS2DBuffer {
self.config.set_image_ext(ext)?;
let channel = self.config.get_format().get_pixel_format();
let tile_size = self.config.get_tile_size();
self.tile_pixels = create_hpx_texture_storage(gl, channel, 128, tile_size)?;

View File

@@ -219,6 +219,9 @@ pub struct HiPS2D {
//#[cfg(feature = "webgl1")]
// layout (location = 0) in vec3 position;
position: Vec<f32>,
//js_position: Float32Array,
//cap: usize,
//ptr: usize,
//#[cfg(feature = "webgl1")]
// layout (location = 1) in vec3 uv_start;
uv_start: Vec<f32>,
@@ -235,7 +238,6 @@ pub struct HiPS2D {
vao: VertexArrayObject,
gl: WebGlContext,
moc: Option<SpaceMoc>,
// A buffer storing the cells in the view
@@ -304,6 +306,7 @@ impl HiPS2D {
let gl = gl.clone();
let moc = None;
let hpx_cells_in_view = vec![];
// request the allsky texture
Ok(Self {
// The image survey texture buffer
@@ -494,6 +497,7 @@ impl HiPS2D {
}
fn recompute_vertices(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) {
//al_core::log(&format!("num position: {:?}", self.position.len()));
self.position.clear();
self.uv_start.clear();
self.uv_end.clear();

View File

@@ -4,11 +4,16 @@ pub mod texture;
use crate::browser_support::BrowserFeaturesSupport;
use crate::healpix::moc::FreqSpaceMoc;
use crate::math::angle::ToAngle;
use crate::math::lonlat;
use crate::math::lonlat::LonLatT;
use crate::math::spectra::SpectralUnit;
use crate::math::spectra::FREQ_MAX;
use crate::math::spectra::FREQ_MIN;
use crate::coosys;
use crate::CooSystem;
use crate::tile_fetcher::TileFetcherQueue;
use al_api::hips::DataproductType;
use al_api::hips::ImageExt;
@@ -400,9 +405,6 @@ impl HiPS3D {
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport,
) {
// update the cursor center before downloading new tiles
self.set_cursor_location(camera.get_center().into(), camera);
// do not add tiles if the view is already at depth 0
let cfg = self.get_config();
let depth_tile = camera
@@ -589,7 +591,7 @@ impl HiPS3D {
let mut start = window_pixel_hash.start.max(domain_pixel_hash.start);
let mut end = window_pixel_hash.end.min(domain_pixel_hash.end);
if start < end {
if start <= end {
start = start - pixel_hash_0 - indices.start;
end = end - pixel_hash_0 - indices.start;
@@ -657,7 +659,15 @@ impl HiPS3D {
crate::event::send_custom_event("spectra", JsValue::from(spectra_js_obj));
}
pub fn set_cursor_location(&mut self, lonlat: LonLatT<f64>, camera: &CameraViewPort) {
pub fn set_cursor_location(&mut self, camera: &CameraViewPort) {
let (lon, lat) = lonlat::xyz_to_radec(&coosys::apply_coo_system(
camera.get_coo_system(),
CooSystem::ICRS,
camera.get_center(),
));
let lonlat = LonLatT(lon, lat);
let cfg = self.get_config();
let s_order = camera
.get_tile_depth()
@@ -915,6 +925,9 @@ impl HiPS3D {
idx + off_indices,
idx + 3 + off_indices,
]);
idx += 4;
// GL LINES
/*self.idx_vertices.extend([
idx + off_indices,
@@ -929,8 +942,6 @@ impl HiPS3D {
idx + 3 + off_indices,
idx + off_indices,
]);*/
idx += 4;
}
off_indices += pos.len() as u16;
@@ -946,23 +957,21 @@ impl HiPS3D {
}
}
{
let mut vao = self.vao.bind_for_update();
vao.update_array(
"position",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.position),
)
.update_array(
"uv",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.uv),
)
.update_element_array(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.idx_vertices),
);
}
let mut vao = self.vao.bind_for_update();
vao.update_array(
"position",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.position),
)
.update_array(
"uv",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.uv),
)
.update_element_array(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.idx_vertices),
);
}
fn reset_available_tiles(&mut self) -> bool {

View File

@@ -11,6 +11,7 @@ use al_core::texture::Texture3D;
use al_core::webgl_ctx::WebGlRenderingCtx;
use cgmath::Vector3;
use fitsrs::hdu::header::Bitpix;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::Range;
use wasm_bindgen::JsValue;
@@ -283,37 +284,45 @@ impl HpxFreqTex {
) -> Result<(), JsValue> {
let raw_bytes = raw_bytes.to_vec().into_boxed_slice();
let (trim1, trim2, trim3, width, height, depth, bitpix, data_byte_offset, bscale, bzero) = {
let fits = FitsImage::from_raw_bytes(&raw_bytes[..])?;
fits[0].insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
self.data = {
let image = FitsImage::from_raw_bytes(&raw_bytes[..])?.pop().unwrap();
image.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
(
fits[0].trim1,
fits[0].trim2,
fits[0].trim3,
fits[0].width,
fits[0].height,
fits[0].depth,
fits[0].bitpix,
fits[0].data_byte_offset.clone(),
fits[0].bscale,
fits[0].bzero,
)
let bitpix = image.bitpix;
let trim = (image.trim1, image.trim2, image.trim3);
let naxis = (image.width, image.height, image.depth);
let bscale = image.bscale;
let bzero = image.bzero;
if let Cow::Owned(uncompressed_bytes) = image.raw_bytes {
Some(HpxFreqData::Fits {
data_byte_offset: 0..uncompressed_bytes.len(),
raw_bytes: uncompressed_bytes.into_boxed_slice(),
bitpix,
trim,
naxis,
bscale,
bzero,
size,
})
} else {
let data_byte_offset = image.data_byte_offset.clone();
std::mem::drop(image);
Some(HpxFreqData::Fits {
raw_bytes,
data_byte_offset,
bitpix,
trim,
naxis,
bscale,
bzero,
size,
})
}
};
let trim = (trim1, trim2, trim3);
let naxis = (width, height, depth);
self.data = Some(HpxFreqData::Fits {
raw_bytes,
data_byte_offset: data_byte_offset.clone(),
bitpix,
trim,
naxis,
bscale,
bzero,
size,
});
self.num_stored_slices = self.num_slices;
self.start_time = Some(Time::now());

View File

@@ -98,7 +98,7 @@ fn get_coord_uv_it(
)
.chain(std::iter::once((
xmax,
if xmax % max_tex_size == 0 {
if xmax.is_multiple_of(max_tex_size) {
1.0
} else {
get_uv_in_tex_chunk(xmax)
@@ -129,7 +129,7 @@ fn get_coord_uv_it(
})
.chain(std::iter::once((
xmax,
if xmax % max_tex_size == 0 {
if xmax.is_multiple_of(max_tex_size) {
1.0
} else {
get_uv_in_tex_chunk(xmax)
@@ -199,7 +199,9 @@ pub fn vertices(
};
x_it.clone().map(move |(x, uvx)| {
let ndc = if let Some(xyz) = wcs.unproj_xyz(&ImgXY::new(x as f64, y as f64)) {
let ndc = if let Some(xyz) =
wcs.unproj_xyz(&ImgXY::new(x as f64 + 0.5, y as f64 + 0.5))
{
let xyz = crate::coosys::apply_coo_system(
CooSystem::ICRS,
camera.get_coo_system(),

View File

@@ -162,7 +162,10 @@ impl Image {
// Compute the fov
let center = wcs
.unproj_lonlat(&ImgXY::new(width as f64 / 2.0, height as f64 / 2.0))
.unproj_lonlat(&ImgXY::new(
(width as f64 / 2.0) + 0.5,
(height as f64 / 2.0) + 0.5,
))
.ok_or(JsValue::from_str("(w / 2, h / 2) px cannot be unprojected"))?;
let center_xyz = center.to_xyz();
let inside = crate::coosys::apply_coo_system(
@@ -172,13 +175,13 @@ impl Image {
);
let vertices = [
wcs.unproj_lonlat(&ImgXY::new(0.0, 0.0))
wcs.unproj_lonlat(&ImgXY::new(0.5, 0.5))
.ok_or(JsValue::from_str("(0, 0) does not lie in the sky"))?,
wcs.unproj_lonlat(&ImgXY::new(width as f64 - 1.0, 0.0))
wcs.unproj_lonlat(&ImgXY::new(width as f64 - 0.5, 0.5))
.ok_or(JsValue::from_str("(w - 1, 0) does not lie in the sky"))?,
wcs.unproj_lonlat(&ImgXY::new(width as f64 - 1.0, height as f64 - 1.0))
wcs.unproj_lonlat(&ImgXY::new(width as f64 - 0.5, height as f64 - 0.5))
.ok_or(JsValue::from_str("(w - 1, h - 1) does not lie in the sky"))?,
wcs.unproj_lonlat(&ImgXY::new(0.0, height as f64 - 1.0))
wcs.unproj_lonlat(&ImgXY::new(0.5, height as f64 - 0.5))
.ok_or(JsValue::from_str("(0, h - 1) does not lie in the sky"))?,
]
.iter()
@@ -669,7 +672,10 @@ impl Image {
// let's redefine the region
let center = self
.wcs
.unproj_lonlat(&ImgXY::new(width as f64 / 2.0, height as f64 / 2.0))
.unproj_lonlat(&ImgXY::new(
(width as f64 / 2.0) + 0.5,
(height as f64 / 2.0) + 0.5,
))
.ok_or(JsValue::from_str("(w / 2, h / 2) px cannot be unprojected"))?;
let center_xyz = center.to_xyz();
let inside = crate::coosys::apply_coo_system(
@@ -680,16 +686,16 @@ impl Image {
let vertices = [
self.wcs
.unproj_lonlat(&ImgXY::new(0.0, 0.0))
.unproj_lonlat(&ImgXY::new(0.5, 0.5))
.ok_or(JsValue::from_str("(0, 0) does not lie in the sky"))?,
self.wcs
.unproj_lonlat(&ImgXY::new(width as f64 - 1.0, 0.0))
.unproj_lonlat(&ImgXY::new(width as f64 - 0.5, 0.5))
.ok_or(JsValue::from_str("(w - 1, 0) does not lie in the sky"))?,
self.wcs
.unproj_lonlat(&ImgXY::new(width as f64 - 1.0, height as f64 - 1.0))
.unproj_lonlat(&ImgXY::new(width as f64 - 0.5, height as f64 - 0.5))
.ok_or(JsValue::from_str("(w - 1, h - 1) does not lie in the sky"))?,
self.wcs
.unproj_lonlat(&ImgXY::new(0.0, height as f64 - 1.0))
.unproj_lonlat(&ImgXY::new(0.5, height as f64 - 0.5))
.ok_or(JsValue::from_str("(0, h - 1) does not lie in the sky"))?,
]
.iter()

View File

@@ -88,7 +88,7 @@ where
pixels_written += num_pixels_to_read;
if F::PIXEL_TYPE.num_channels() == 1 && y % step_cut == 0 {
if F::PIXEL_TYPE.num_channels() == 1 && y.is_multiple_of(step_cut) {
// on a good line
let bytes_line = &buf[id_tx][off_bytes_dst..(off_bytes_dst + num_bytes_to_read)];
for x_in_patch in (0..w_patch).step_by(step_cut) {
@@ -153,7 +153,7 @@ where
}
}
if (((dy + 1) % (max_tex_size as usize) == 0) && id_tx == buf.len() - 1)
if ((dy + 1).is_multiple_of(max_tex_size as usize) && id_tx == buf.len() - 1)
|| pixels_written >= num_pixels
{
// we can create new textures of size max_tex_size

View File

@@ -351,29 +351,6 @@ impl Layers {
}
}
pub fn rename_layer(&mut self, layer: &str, new_layer: &str) -> Result<(), JsValue> {
let err_layer_not_found =
JsValue::from_str(&format!("Layer {layer:?} not found, so cannot be removed."));
// layer from layers does also need to be removed
let id_layer = self
.layers
.iter()
.position(|l| layer == l)
.ok_or(err_layer_not_found.clone())?;
self.layers[id_layer] = new_layer.to_string();
let meta = self.meta.remove(layer).ok_or(err_layer_not_found.clone())?;
let id = self.ids.remove(layer).ok_or(err_layer_not_found)?;
// Add the new
self.meta.insert(new_layer.to_string(), meta);
self.ids.insert(new_layer.to_string(), id);
Ok(())
}
pub fn swap_layers(&mut self, first_layer: &str, second_layer: &str) -> Result<(), JsValue> {
let id_first_layer =
self.layers
@@ -454,7 +431,11 @@ impl Layers {
// HiPS cube
DataproductType::Cube => HiPS::D3(HiPS3D::new(cfg, gl, &layer)?),
// HiPS 3D
DataproductType::SpectralCube => HiPS::D3(HiPS3D::new(cfg, gl, &layer)?),
DataproductType::SpectralCube => {
let mut hips = HiPS3D::new(cfg, gl, &layer)?;
hips.set_cursor_location(camera);
HiPS::D3(hips)
}
// Typical HiPS image
_ => HiPS::D2(HiPS2D::new(cfg, gl)?),
};

View File

@@ -28,4 +28,16 @@ where
pub fn is_ccw(&self) -> bool {
crate::math::utils::ccw_tri(self.v1, self.v2, self.v3)
}
pub fn is_elongated(&self) -> bool {
let mag2_12 = (self.v1[0] - self.v2[0]) * (self.v1[0] - self.v2[0])
+ (self.v1[1] - self.v2[1]) * (self.v1[1] - self.v2[1]);
let mag2_13 = (self.v1[0] - self.v3[0]) * (self.v1[0] - self.v3[0])
+ (self.v1[1] - self.v3[1]) * (self.v1[1] - self.v3[1]);
let mag2_23 = (self.v2[0] - self.v3[0]) * (self.v2[0] - self.v3[0])
+ (self.v2[1] - self.v3[1]) * (self.v2[1] - self.v3[1]);
let l = S::from(0.2).unwrap();
mag2_12 >= l || mag2_23 >= l || mag2_13 >= l
}
}

7903
src/core/src/shaders.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -69,6 +69,7 @@ impl HiPSLocalFiles {
ImageExt::Jpeg => &self.tiles[1],
ImageExt::Png => &self.tiles[2],
ImageExt::Webp => &self.tiles[3],
ImageExt::FitsFz => todo!(),
};
tiles_per_fmt[d].get(&i)

View File

@@ -50,15 +50,9 @@ pub unsafe fn transmute_boxed_slice<I, O>(s: Box<[I]>) -> Box<[O]> {
Box::from_raw(out_slice_ptr)
}
#[allow(dead_code)]
pub unsafe fn transmute_vec_to_u8<I>(mut s: Vec<I>) -> Vec<u8> {
s.set_len(std::mem::size_of_val(&s[..]));
std::mem::transmute(s)
}
#[allow(dead_code)]
pub unsafe fn transmute_vec<I, O>(mut s: Vec<I>) -> Result<Vec<O>, &'static str> {
if std::mem::size_of::<I>() % std::mem::size_of::<O>() > 0 {
if !std::mem::size_of::<I>().is_multiple_of(std::mem::size_of::<O>()) {
Err("The input type is not a multiple of the output type")
} else {
s.set_len(s.len() * (std::mem::size_of::<I>() / std::mem::size_of::<O>()));

View File

@@ -5,6 +5,7 @@
/* disable x swipe on chrome, firefox */
/* see. https://stackoverflow.com/questions/30636930/disable-web-page-navigation-on-swipeback-and-forward */
overscroll-behavior-x: none;
overscroll-behavior-y: none; /* Prevents vertical pull-to-refresh */
/* Hide the draggable boxes that goes out of the view */
overflow: hidden;
/* media query on the aladin lite container. not supported everywhere.
@@ -18,9 +19,64 @@
--aladin-color-border: #fff;
}
.aladin-dark-theme {
background-color: black;
color: white;
[data-theme="dark"] {
--bg-color: black;
--text-color: #ececec;
--border-color: #ececec;
--hover-color: greenyellow;
--toggle-color: dodgerblue;
--valid-color: greenyellow;
--error-color: red;
--border-size: 1px;
}
[data-theme="light"] {
--bg-color: #ececec;
--text-color: #212121;
--border-color: #212121;
--hover-color: green;
--toggle-color: dodgerblue;
--border-size: 2px;
--valid-color: greenyellow;
--error-color: red;
}
.aladin-tree {
width: 100%;
border-top: 1px solid var(--border-color);
border-bottom: 2px solid var(--border-color);
}
.aladin-tree .aladin-directory-path {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
width: 100%;
border-bottom: 2px solid var(--border-color);
}
.aladin-tree ul {
padding:0;
margin:0;
overflow-y: scroll;
height: 300px;
}
.aladin-link {
list-style-type: none;
padding: 0.5em 0px;
cursor:pointer;
margin: 0;
}
.aladin-tree li:hover {
color: var(--hover-color);
}
.aladin-tree li:last-child {
border-bottom: none;
}
.aladin-tree li {
border-bottom: 1px solid var(--border-color);
}
.aladin-lite-spectra-displayer .aladin-spectra-unit-selector {
@@ -90,16 +146,18 @@
.aladin-measurement-div {
font-family: monospace;
display: block;
border-top-left-radius: 0;
max-height: 30vh;
max-width: 100%;
overflow-x: auto;
overflow-y: auto;
white-space: nowrap;
border-radius: 3px;
border-top-left-radius: 0;
overflow: auto;
white-space: nowrap;
max-height: 200px;
overscroll-behavior: contain;
scroll-behavior: auto;
}
.aladin-btn.tab {
@@ -109,9 +167,11 @@
}
.aladin-measurement-div table {
border-collapse: collapse;
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
white-space: nowrap;
min-width: 100%;
width: max-content;
}
.aladin-measurement-div table::-webkit-scrollbar {
@@ -119,26 +179,23 @@
}
.aladin-measurement-div table thead {
position: sticky;
width: 100%;
top: 0;
position: sticky; /* optional but helps */
}
.aladin-measurement-div.aladin-dark-theme table thead {
background-color: #000;
color: white;
}
.aladin-measurement-div table th {
.aladin-measurement-div table thead th {
position: sticky;
padding: 0.3em 0.5em;
border-bottom: var(--border-size) solid var(--border-color);
background-color: var(--bg-color);
color: var(--text-color);
}
.aladin-measurement-div table tr td a {
color: green;
}
.aladin-measurement-div table tr td a:hover {
color: greenyellow;
color: var(--hover-color);
}
.aladin-measurement-div table tr td {
@@ -231,25 +288,19 @@
.aladin-box {
padding: 0.2rem;
background: whitesmoke;
border-radius: 2px;
position: absolute;
font-size: inherit;
font-family: monospace;
background: #fff;
line-height: 1.3;
color: #222;
/*box-shadow: 0 0 6px rgba(0,0,0,0.2);*/
/* Allow scrolling but disable scroll bar */
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
/*overflow-y: auto;*/
overflow-y: none;
max-height: 500px;
/*max-width: fit-content;*/
height: fit-content;
height: min-content;
width: min-content;
border: var(--border-size) solid var(--border-color);
}
.aladin-container canvas {
@@ -281,7 +332,7 @@
.aladin-indicatorBtn {
color: #605F61!important;
border: 1px solid #AEAEAE!important;
border: var(--border-size) solid #AEAEAE!important;
border-radius: 3px;
font-size: 1rem;
@@ -302,7 +353,7 @@
margin-top: 0 0 2px 0;
cursor:pointer;
color: #605F61;
border: 1px solid #AEAEAE;
border: var(--border-size) solid #AEAEAE;
border-radius: 3px;
background: #fff;
font-size: 1.0rem;
@@ -318,7 +369,7 @@
.aladin-box-separator {
height: 0;
border-top: 1px solid #d2d2d2;
margin: 5px 0px 5px -4px;
padding-bottom: 5px;
}
.aladin-restore {
@@ -348,19 +399,19 @@
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid #484848;
border: var(--border-size) solid var(--border-color);
border-radius: 3px;
color: black;
background-color: #bababa;
color: var(--text-color);
background-color: var(--bg-color);
font-family: monospace;
}
.aladin-btn.aladin-dark-theme.toggled {
border-color: dodgerblue;
.aladin-btn.toggled {
border-color: var(--toggle-color);
}
.aladin-btn.aladin-dark-theme:hover, .aladin-input-select.aladin-dark-theme:hover {
border-color: greenyellow;
.aladin-btn:hover, .aladin-input-select:hover {
border-color: var(--hover-color);
}
.aladin-btn.disabled {
@@ -387,36 +438,39 @@
height: 100%;
}
/*
.aladin-icon.aladin-dark-theme {
background-color: transparent;
}
*/
.aladin-icon.aladin-dark-theme.aladin-icon-monochrome img {
[data-theme="dark"] .aladin-icon.aladin-icon-monochrome img {
filter: invert(100%) sepia(91%) saturate(0%) hue-rotate(169deg) brightness(115%) contrast(100%);
}
.aladin-btn.aladin-dark-theme {
background-color: rgba(0, 0, 0, 0.5);
border-color: white;
.aladin-btn {
background-color: var(--bg-color);
border-color: var(--border-color);
}
.aladin-measurement-div.aladin-dark-theme {
background-color: rgba(0, 0, 0, 0.8);
border: 1px solid white;
.aladin-measurement-div {
background-color: var(--bg-color);
border: var(--border-size) solid var(--border-color);
}
.aladin-measurement-div.aladin-dark-theme table {
color: white;
.aladin-measurement-div table {
color: var(--text-color);
}
.aladin-box.aladin-dark-theme {
background-color: rgba(0, 0, 0, 0.8);
color: white;
.aladin-box {
background-color: var(--bg-color);
color: var(--text-color);
}
.aladin-input-select.aladin-dark-theme {
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid white;
.aladin-input-select {
background-color: var(--bg-color);
border: var(--border-size) solid var(--text-color);
color: var(--text-color);
}
.aladin-container .small-sized-icon {
@@ -437,16 +491,33 @@
height: 1.2rem;
}
.aladin-input-text.aladin-dark-theme:focus, .aladin-input-number.aladin-dark-theme:focus {
border-color: dodgerblue;
.aladin-input-text:focus, .aladin-input-number:focus {
border-color: var(--toggle-color);
}
.aladin-input-text.aladin-dark-theme.search {
text-shadow: 0px 0px 2px #000;
[data-theme="light"] .aladin-input-text.search:focus {
background-image: url(../../assets/icons/search.svg);
background-size: 1.8rem;
background-repeat: no-repeat;
padding-left: 1.8rem;
}
.aladin-input-text.search:focus, .aladin-input-text.search:hover {
background-image: url(../../assets/icons/search-white.svg);
[data-theme="light"] .aladin-input-text.search:hover {
background-image: url(../../assets/icons/search.svg);
background-size: 1.8rem;
background-repeat: no-repeat;
padding-left: 1.8rem;
}
[data-theme="dark"] .aladin-input-text.search:focus {
background-image: url(../../assets/icons/search-dark.svg);
background-size: 1.8rem;
background-repeat: no-repeat;
padding-left: 1.8rem;
}
[data-theme="dark"] .aladin-input-text.search:hover {
background-image: url(../../assets/icons/search-dark.svg);
background-size: 1.8rem;
background-repeat: no-repeat;
padding-left: 1.8rem;
@@ -464,22 +535,12 @@
box-shadow:inset 0px 0px 0px 1px #f00;
}
.aladin-input-text.aladin-dark-theme.aladin-not-valid {
border: 1px solid red;
.aladin-input-text.aladin-not-valid {
border: var(--border-size) solid var(--error-color);
}
.aladin-input-text.aladin-dark-theme.aladin-valid {
border: 1px solid yellowgreen;
}
.aladin-cancelBtn {
background-color: #ca4242;
border-color: #bd3935;
}
.aladin-validBtn {
background-color: #42c739;
border-color: #38a730;
.aladin-input-text.aladin-valid {
border: var(--border-size) solid var(--valid-color);
}
.aladin-box-content > div {
@@ -508,11 +569,11 @@
}
.aladin-vertical-list > *:first-child {
margin-top: 0;
padding-top: 0;
}
.aladin-vertical-list > * {
margin-top: 0.5rem;
padding-top: 0.5rem;
}
.aladin-horizontal-list {
@@ -576,7 +637,7 @@
.aladin-popup {
/*font-family: Verdana, Geneva, Tahoma, sans-serif;*/
background: white;
border: 1px solid #bbb;
border: var(--border-size) solid #bbb;
border-radius: 2px;
padding: 4px;
top: 80px;
@@ -713,12 +774,8 @@
margin: 0;
padding: 0;
/*font-family: Verdana, Geneva, Tahoma, sans-serif;*/
border-radius: 3px;
border-bottom: 1px solid white;
border-right: 1px solid white;
border-bottom: var(--border-size) solid var(--border-color);
border-right: var(--border-size) solid var(--border-color);
}
.aladin-context-menu .aladin-context-menu-item {
@@ -731,29 +788,22 @@
padding: 0.2rem 0.4rem;
position: relative;
box-shadow:inset 1px 1px 0px 0px #fff;
box-shadow:inset var(--border-size) var(--border-size) 0px 0px var(--border-color);
background-color: rgba(0, 0, 0, 0.9);
background-color: var(--bg-color);
margin: 0;
box-sizing: content-box;
line-height: normal;
}
.aladin-context-menu .aladin-context-menu-item:first-of-type {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.aladin-context-menu .aladin-context-menu-item:last-of-type {
border-bottom: 0;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
.aladin-context-menu .aladin-context-menu-item.aladin-context-menu-item-disabled {
cursor: not-allowed;
color: red;
color: var(--error-color);
}
.aladin-context-menu .aladin-context-menu-item.aladin-context-menu-item-selected::after {
@@ -766,11 +816,11 @@
}
.aladin-context-menu-item:hover {
color: greenyellow;
color: var(--hover-color);
}
.aladin-context-menu-item:not(hover) {
color: white;
color: var(--text-color);
}
.aladin-context-menu-item .aladin-context-sub-menu {
@@ -784,15 +834,6 @@
text-overflow: ellipsis;
}
/*.aladin-context-menu-item:first-of-type {
border-top: 1px solid #d2d2d2;
margin-top: -1px;
}*/
/*
.aladin-context-menu .aladin-context-menu-item:hover > .aladin-context-sub-menu {
display: block;
}
*/
.aladin-context-menu .aladin-context-sub-menu.left {
left: 0;
transform: translateX(-100%);
@@ -847,9 +888,10 @@
box-sizing: border-box;
}
.aladin-input-text.aladin-dark-theme, .aladin-input-number.aladin-dark-theme {
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid white;
.aladin-input-text, .aladin-input-number {
background-color: var(--bg-color);
border: var(--border-size) solid var(--border-color);
color: var(--text-color)
}
.aladin-input-text::placeholder {
@@ -860,7 +902,7 @@
/* Input select */
.aladin-input-select {
/* Reset */
border: 1px solid white;
border: var(--border-size) solid var(--border-color);
outline: 0;
/*font: inherit;*/
/* Personalize */
@@ -943,58 +985,20 @@ otherwise it fits its content options. If those are too big the select can go ou
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
.aladin-input-range::-webkit-slider-container {
background: white;
background: var(--text-color);
height: 0.1rem;
min-height: 0.1rem;
}
/*
.aladin-input-range-datalist {
-webkit-appearance: none;
appearance: none;
display: none;
display: flex;
position: absolute;
width: 100%;
padding:0;
margin:0;
height: 0.1rem;
top: 0rem;
pointer-events: none;
}
.aladin-input-range-datalist option {
-webkit-appearance: none;
appearance: none;
position: absolute;
transform: translate(-50%, 0);
justify-content: center;
text-align: center;
width: 0.1rem;
border-radius: 0.1rem;
height: 0.1rem;
padding: 0;
margin: 0;
background: #D3D3D3;
}*/
.aladin-dark-theme {
color: white;
}
/* *********************************************** */
/* Tooltip */
.aladin-tooltip-container {
position: relative;
/* take the size of its inner div child */
font-family: monospace;
line-height: 1rem;
/*z-index: 100;*/
float: left;
}
@@ -1016,6 +1020,9 @@ otherwise it fits its content options. If those are too big the select can go ou
/* Position the tooltip text - see examples below! */
position: absolute;
color: var(--text-color);
background-color: var(--bg-color);
border: var(--border-size) solid var(--border-color);
/*font-family: Verdana, Geneva, Tahoma, sans-serif;*/
@@ -1117,6 +1124,15 @@ otherwise it fits its content options. If those are too big the select can go ou
left: 0.2rem;
}
.aladin-link {
color: var(--text-color);
}
.aladin-link:hover {
color: var(--hover-color);
text-decoration: underline;
}
.aladin-simbadPointer-control {
position: absolute;
top: 7.8rem;
@@ -1143,17 +1159,21 @@ otherwise it fits its content options. If those are too big the select can go ou
font-family: monospace;
color: white;
color: var(--text-color);
border-radius: 0.4rem;
}
.aladin-stack-box {
width: 17rem;
min-width: 17rem;
}
.aladin-item-selected {
border: var(--border-size) solid orange;
}
.aladin-HiPS-filter-box {
margin-top: 0.4rem;
/*width: 250px;*/
}
.aladin-HiPS-filter-box .aladin-horizontal-list {
@@ -1180,7 +1200,7 @@ otherwise it fits its content options. If those are too big the select can go ou
left: 4.75rem;
font-family: monospace;
color: white;
color: var(--text-color);
border-radius: 0.2rem;
}
@@ -1201,6 +1221,18 @@ otherwise it fits its content options. If those are too big the select can go ou
border-right: none;
}
.aladin-fov {
background-color: transparent;
}
[data-theme="light"] .aladin-fov {
color: white;
}
[data-theme="dark"] .aladin-fov {
color: var(--text-color);
}
.aladin-fov {
position: absolute;
bottom: 0.2rem;
@@ -1232,13 +1264,9 @@ otherwise it fits its content options. If those are too big the select can go ou
transform: translate(-50%, 0);
}
.aladin-status-bar.aladin-dark-theme {
color: white;
background-color: black;
}
.aladin-fov.aladin-dark-theme {
background-color: rgba(0, 0, 0, 0.5);
.aladin-status-bar{
color: var(--text-color);
background-color: var(--bg-color);
}
.aladin-table {
@@ -1248,8 +1276,8 @@ otherwise it fits its content options. If those are too big the select can go ou
max-width: calc(100% - 0.4rem);
line-height: 1rem;
}
.aladin-measurement-div.aladin-dark-theme {
color: white;
.aladin-measurement-div {
color: var(--text-color);
}
.aladin-share-control {
@@ -1310,22 +1338,4 @@ otherwise it fits its content options. If those are too big the select can go ou
.aladin-cooFrame {
display: none;
}
}
/*@media screen and (max-width: 31rem) {
.aladin-projection-control {
display: none;
}
}*/
/*@container (max-width: 40rem) {
.aladin-input-text.aladin-dark-theme.search {
width: 6rem;
text-shadow: 0px 0px 2px #000;
}
}
@container (max-width: 31rem) {
.aladin-fov .aladin-monospace-text {
display: none;
}
}*/
}

View File

@@ -97,6 +97,29 @@ A.aladin = function (divSelector, options) {
divElement = divSelector;
}
let retrieveDefaultMode = () => {
const storedPreference = localStorage.getItem("theme");
const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const theme = storedPreference || (systemPrefersDark ? "dark" : "light");
return theme;
}
let theme;
if (options.mode) {
let mode = options.mode.toLowerCase();
if (mode === 'dark' || mode === 'light') {
theme = mode;
} else {
console.warn("Interface mode option not recognized, only `dark` or `light` are possible values.")
theme = retrieveDefaultMode()
}
} else {
theme = retrieveDefaultMode()
}
// Retrieve the data-theme from localStorage or system preferences
divElement.setAttribute("data-theme", theme);
// Associate the CSS inside the div
var cssStyleSheet = document.createElement('style')
cssStyleSheet.classList.add("aladin-css");
@@ -362,7 +385,7 @@ A.graphicOverlay = function (options) {
* @returns {ProgressiveCat} Returns a new Overlay object representing the graphic overlay.
*
* @example
* let gaia = A.catalogHiPS('http://axel.u-strasbg.fr/HiPSCatService/I/345/gaia2', {onClick: 'showTable', color: 'orange', name: 'Gaia', filter: myFilterFunction});
* let gaia = A.catalogHiPS('http://axel.cds.unistra.fr/HiPSCatService/I/345/gaia2', {onClick: 'showTable', color: 'orange', name: 'Gaia', filter: myFilterFunction});
* aladin.addCatalog(gaia)
*/
A.catalogHiPS = function (url, options) {

View File

@@ -152,6 +152,7 @@ import { Polyline } from "./shapes/Polyline";
* @property {boolean} [realFullscreen=false] - Whether to use real fullscreen mode.
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
* @property {boolean} [manualSelection=false] - When set to true, no selection will be performed, only events will be generated.
* @property {string} [mode] - Interface theme, can be either 'dark' or 'light'. If not set, the mode will be retrieved from your browser preference or your localStorage.
* @property {Object} [selector] - More options for the the selector.
* @property {string} [selector.color] - Color of the selector, defaults to the color of the reticle. Can be a hex color or a function returning a hex color.
* @property {number} [selector.lineWidth=2] - Width of the selector line.
@@ -749,7 +750,7 @@ export let Aladin = (function () {
longitudeReversed: false,
realFullscreen: false,
pixelateCanvas: true,
manualSelection: false
manualSelection: false,
};
/**
@@ -941,7 +942,7 @@ export let Aladin = (function () {
objectName +
"'";
var url =
"//simbad.u-strasbg.fr/simbad/sim-tap/sync?query=" +
"//simbad.cds.unistra.fr/simbad/sim-tap/sync?query=" +
encodeURIComponent(query) +
"&request=doQuery&lang=adql&format=json&phase=run";
@@ -2004,7 +2005,7 @@ export let Aladin = (function () {
* </ul>
*/
Aladin.prototype.setBaseImageLayer = function (urlOrHiPSOrFITS) {
return this.setOverlayImageLayer(urlOrHiPSOrFITS, "base");
return this.setOverlayImageLayer(urlOrHiPSOrFITS, (this.view.overlayLayers && this.view.overlayLayers[0]) || Utils.uuidv4());
};
/**
@@ -2014,7 +2015,7 @@ export let Aladin = (function () {
* @returns {HiPS|Image} - Returns the image layer corresponding to the base layer
*/
Aladin.prototype.getBaseImageLayer = function () {
return this.view.getImageLayer("base");
return this.view.getImageLayer(this.view.overlayLayers && this.view.overlayLayers[0]);
};
/**
@@ -3003,7 +3004,7 @@ aladin.customizeShareURLFunction(() => {return 'https://sky.esa.int/esasky/?targ
* @param {Function} [successCallback=<center the view on the FITS file>] - The callback function to be executed on a successful display.
* The callback gives the ra, dec, and fov of the image; By default, it centers the view on the FITS file loaded.
* @param {Function} [errorCallback] - The callback function to be executed if an error occurs during display.
* @param {string} [layer="base"] - The name of the layer. If not specified, it will be replace the base layer.
* @param {string} [layer] - The name of the layer. If not specified, it will be replace the base layer.
*
* @example
aladin.displayFITS(
@@ -3027,7 +3028,7 @@ aladin.displayFITS(
options,
successCallback,
errorCallback,
layer = "base"
layer
) {
successCallback =
successCallback ||
@@ -3041,6 +3042,7 @@ aladin.displayFITS(
successCallback,
errorCallback
);
layer = layer || (this.view.overlayLayers && this.view.overlayLayers[0])
return this.setOverlayImageLayer(image, layer);
};
@@ -3117,7 +3119,6 @@ aladin.displayFITS(
get("https://alasky.unistra.fr/cgi/fits2HiPS", data).then(
async (response) => {
console.log(response, data)
if (response.status != "success") {
console.error("An error occured: " + response.message);
if (errorCallback) {

View File

@@ -59,9 +59,9 @@
}
this.maxCut = {
webp: 1.0,
jpeg: 1.0,
png: 1.0,
webp: 255.0,
jpeg: 255.0,
png: 255.0,
fits: undefined // wait the default value coming from the properties
};
if (options && Number.isFinite(options.maxCut)) {
@@ -95,6 +95,16 @@
}
}
let minCut = this.minCut[this.imgFormat]
if (this.imgFormat !== "fits") {
minCut /= 255.0
}
let maxCut = this.maxCut[this.imgFormat]
if (this.imgFormat !== "fits") {
maxCut /= 255.0
}
// Reset the whole meta object
return {
blendCfg: blend,
@@ -107,8 +117,8 @@
kContrast: this.kContrast,
stretch: this.stretch,
minCut: this.minCut[this.imgFormat],
maxCut: this.maxCut[this.imgFormat],
minCut,
maxCut,
reversed: this.reversed,
cmapName: this.colormap,
}
@@ -121,7 +131,7 @@
this.setColormap(options.colormap, options)
this.setCuts(options.minCut, options.maxCut)
this.setCuts(options.minCut, options.maxCut, options.cutFormat)
this.setBrightness(options.brightness)
this.setSaturation(options.saturation)
@@ -249,18 +259,20 @@
};
// Sets the cuts for the current image format
ColorCfg.prototype.setCuts = function(minCut, maxCut) {
ColorCfg.prototype.setCuts = function(minCut, maxCut, imgFormat) {
imgFormat = imgFormat || this.imgFormat;
if (minCut instanceof Object) {
// Mincut is given in the form of an javascript object with all the formats
this.minCut = minCut
this.minCut = {...this.minCut, ...minCut};
} else if (minCut !== null && minCut !== undefined) {
this.minCut[this.imgFormat] = minCut;
this.minCut[imgFormat] = minCut;
}
if (maxCut instanceof Object) {
this.maxCut = maxCut;
this.maxCut = {...this.maxCut, ...maxCut};
} else if (maxCut !== null && maxCut !== undefined) {
this.maxCut[this.imgFormat] = maxCut;
this.maxCut[imgFormat] = maxCut;
}
};

View File

@@ -50,6 +50,7 @@ export class PolySelect extends FSM {
view.aladin.removeStatusBarMessage('selector')
}
let btn;
let mouseout = (params) => {
let {e, coo} = params;
@@ -215,7 +216,7 @@ export class PolySelect extends FSM {
};
let fsm;
if (Utils.hasTouchScreen()) {
//if (Utils.hasTouchScreen()) {
let mousedown = click;
let mouseup = click;
@@ -259,7 +260,7 @@ export class PolySelect extends FSM {
}
}
}
} else {
/*} else {
// desktop, laptops...
fsm = {
state: 'off',
@@ -296,7 +297,7 @@ export class PolySelect extends FSM {
}
}
}
}
}*/
super(fsm)
let self = this;

View File

@@ -31,7 +31,6 @@ import { HiPSProperties } from "./HiPSProperties.js";
import { Aladin } from "./Aladin.js";
import { CooFrameEnum } from "./CooFrameEnum.js";
import { Utils } from "./Utils"
import { SpectraDisplayer } from "./SpectraDisplayer.js";
let PropertyParser = {};
// Utilitary functions for parsing the properties and giving default values
@@ -180,13 +179,17 @@ PropertyParser.isPlanetaryBody = function (properties) {
* @property {number} [brightness=0.0] - The brightness value for the color configuration.
* @property {number} [contrast=0.0] - The contrast value for the color configuration.
* @property {string} [requestMode='cors'] - Determines how the request will interact with cross-origin resources.
* - 'cors' - allow cross-origin requests with proper CORS headers.
* - 'no-cors' - send the request without CORS.
* - 'same-origin' - only allow requests to the same origin.
* @property {string} [requestCredentials='omit'] - Specifies whether to send cookies and HTTP credentials with the request.
* - 'omit' - never send credentials.
* - 'same-origin' - send only for same-origin requests.
* - 'include' - always send, even for cross-origin requests.
* <ul>
* <li>'cors' - allow cross-origin requests with proper CORS headers.</li>
* <li>'no-cors' - send the request without CORS.</li>
* <li>'same-origin' - only allow requests to the same origin.</li>
* </ul>
* @property {string} [requestCredentials='same-origin'] - Specifies whether to send cookies and HTTP credentials with the request.
* <ul>
* <li>'omit' - never send credentials.</li>
* <li>'same-origin' - send only for same-origin requests.</li>
* <li>'include' - always send, even for cross-origin requests.</li>
* </ul>
*/
/**
@@ -270,7 +273,7 @@ export let HiPS = (function () {
this.name = (options && options.name) || id;
this.startUrl = options.startUrl;
this.requestMode = options && options.requestMode || 'cors';
this.requestCredentials = options && options.requestCredentials || 'omit';
this.requestCredentials = options && options.requestCredentials || 'same-origin';
this.slice = 0;
@@ -729,9 +732,16 @@ export let HiPS = (function () {
*
* @param {number} minCut - The low cut value to set for the HiPS.
* @param {number} maxCut - The high cut value to set for the HiPS.
* @param {string} [imgFormat] - The image format for which one wants to set the cuts. By default, the format used is the current imageFormat
*/
HiPS.prototype.setCuts = function (minCut, maxCut) {
this.setOptions({minCut, maxCut})
HiPS.prototype.setCuts = function (minCut, maxCut, imgFormat) {
imgFormat = imgFormat?.toLowerCase();
if (imgFormat === "jpg") {
imgFormat = "jpeg";
}
this.setOptions({minCut, maxCut, cutFormat: imgFormat})
};
/**
@@ -908,7 +918,7 @@ export let HiPS = (function () {
imgFormat = "jpeg";
}
if (!["fits", "png", "jpeg", "webp"].includes(imgFormat)) {
if (!["fits", "png", "jpeg", "webp", "fits.fz"].includes(imgFormat)) {
console.warn('Formats must lie in ["fits", "png", "jpg", "webp"]. imgFormat option property ignored');
} else {
// Passed the check, we erase the image format with the new one
@@ -922,7 +932,7 @@ export let HiPS = (function () {
this.imgFormat = imgFormat;
let [minCut, maxCut] = this.getCuts();
if (minCut === undefined && maxCut === undefined && imgFormat === "fits") {
if (minCut === undefined && maxCut === undefined && (imgFormat === "fits" || imgFormat === "fits.fz")) {
// sets the default cuts parsed from the properties
this.setCuts(this.defaultFitsMinCut, this.defaultFitsMaxCut)
}

View File

@@ -402,6 +402,7 @@ export let Image = (function () {
// Set the automatic computed cuts
let [minCut, maxCut] = self.getCuts();
minCut = minCut || imageParams.min_cut;
maxCut = maxCut || imageParams.max_cut;
self.setCuts(

View File

@@ -77,7 +77,7 @@ export let MeasurementTable = (function() {
tooltip: {
global: true,
aladin: this.aladin,
content: 'Press Shift + mouse wheel for scrolling horizontally'
content: 'Press shift + mouse wheel for scrolling'
},
aladin: this.aladin,
layout,

View File

@@ -51,7 +51,7 @@ export class MocServer {
//expr: "dataproduct_type=image",
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,client_category",
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
};

View File

@@ -60,7 +60,7 @@ export let SimbadPointer = (function() {
if (Utils.isNumber(magnitude)) {
content += '<em>Mag: </em>' + magnitude + '<br>';
}
content += '<br><a target="_blank" href="http://cdsportal.u-strasbg.fr/?target=' + encodeURIComponent(objName) + '">Query in CDS portal</a>';
content += '<br><a target="_blank" href="http://cdsportal.cds.unistra.fr/?target=' + encodeURIComponent(objName) + '">Query in CDS portal</a>';
content += '</div>';
aladinInstance.showPopup(objCoo.lon, objCoo.lat, title, content);

View File

@@ -21,7 +21,6 @@ import { ActionButton } from "./gui/Widgets/ActionButton";
import { Input } from "./gui/Widgets/Input";
import HomeIconUrl from '../../assets/icons/maximize.svg';
import SpectraIconUrl from '../../assets/icons/freq.svg';
import { ALEvent } from "./events/ALEvent";
import { Utils } from "./Utils";
import { Aladin } from "./Aladin";
@@ -142,6 +141,7 @@ export class SpectraDisplayer {
this.minY = undefined;
this.maxY = undefined;
this.mouseFreq = undefined;
this.enabled = true;
// One canvas for the spectra
this.canvas = createPlotCanvas("spectra-line");
@@ -169,7 +169,7 @@ export class SpectraDisplayer {
SpectraDisplayer.UNIT.VELOCITY.label,
],
tooltip: {
content: "Unit between frequency, wavelength and velocity",
content: `Unit: ${SpectraDisplayer.UNIT.FREQUENCY.label}, ${SpectraDisplayer.UNIT.WAVELENGTH.label} and ${SpectraDisplayer.UNIT.VELOCITY.label}`,
position: {direction: "right"}
},
change: (e) => {
@@ -246,7 +246,7 @@ export class SpectraDisplayer {
}
defineEventListeners() {
let lastMouse = { x: 0, y: 0 };
this.lastMouse = { x: 0, y: 0 };
this.isDragging = false;
let canvas = this.canvas;
@@ -257,32 +257,39 @@ export class SpectraDisplayer {
let lastClickTime = 0;
const DOUBLE_CLICK_DELAY = 300; // most operating systems uses duration between 250ms and 500ms by default.
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
let mouseDownTime = 0;
let mouseDownPos = { x: 0, y: 0 };
const CLICK_TIME_THRESHOLD = 250; // ms
const CLICK_MOVE_THRESHOLD = 5; // pixels
Utils.on(canvas, 'mousedown touchstart', (e) => {
mouseDownTime = Date.now();
mouseDownPos = Utils.relMouseCoords(e);
const mx = mouseDownPos.x;
const my = mouseDownPos.y;
let v = this.data.values[Math.round(mx / this.scaleX)]
let len = this.data.values.length;
v = this.height - (v - this.minY) * this.scaleY
if (my >= v) {
this.isDragging = true;
lastMouse = { x: mx, y: my };
this.lastMouse = { x: mx, y: my };
canvas.style.cursor = 'grabbing';
} else {
// check if the click is next to the center bar
// Draw the vertical line that can be grabed to move the slice
this.ctx.beginPath();
this.ctx.lineWidth = 30;
this.ctx.moveTo(this.scaleX * len / 2, this.height);
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
this.ctx.strokeStyle = Aladin.DEFAULT_OPTIONS.reticleColor;
this.ctx.lineWidth = 10;
if (this.ctx.isPointInStroke(mx, my)) {
this.isDragging = true;
lastMouse = { x: mx, y: my };
this.lastMouse = { x: mx, y: my };
canvas.style.cursor = 'grabbing';
} else {
// propagate event to its sibling
@@ -298,9 +305,17 @@ export class SpectraDisplayer {
altKey: e.altKey,
metaKey: e.metaKey,
button: e.button,
changedTouches: e.changedTouches,
targetTouches: e.targetTouches,
relatedTarget: e.relatedTarget,
};
const event = new MouseEvent('mousedown', paramsEvent);
let event;
if (e.type === "mousedown") {
event = new MouseEvent("mousedown", paramsEvent);
} else {
this.disableInteraction();
event = new TouchEvent("touchstart", paramsEvent)
}
// Track timing to simulate dblclick
const now = Date.now();
if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
@@ -322,16 +337,43 @@ export class SpectraDisplayer {
}
});
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
Utils.on(canvas, 'mousemove touchmove', (e) => {
if (!this.enabled) {
let paramsEvent = {
bubbles: e.bubbles,
cancelable: e.cancelable,
clientX: e.clientX,
clientY: e.clientY,
screenX: e.screenX,
screenY: e.screenY,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
button: e.button,
changedTouches: e.changedTouches,
targetTouches: e.targetTouches,
relatedTarget: e.relatedTarget,
};
let touchEvent = new TouchEvent("touchmove", paramsEvent);
this.view.catalogCanvas.dispatchEvent(touchEvent);
return;
}
let mouseXY = Utils.relMouseCoords(e)
const mx = mouseXY.x;
const my = mouseXY.y;
// can be in the spectral area
let v = this.data.values[Math.round(mx / this.scaleX)]
let len = this.data.values.length;
v = this.height - (v - this.minY) * this.scaleY
if (!this.isDragging) {
this.isDragging = canvas.style.cursor === 'grabbing';
}
canvas.style.cursor = 'default';
this.ctxCursor.clearRect(0, 0, this.width, this.height);
@@ -363,7 +405,7 @@ export class SpectraDisplayer {
this.ctx.moveTo(this.scaleX * len / 2, this.height);
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
this.ctx.strokeStyle = "red";
this.ctx.lineWidth = 10;
this.ctx.lineWidth = 30;
if (this.ctx.isPointInStroke(mx, my)) {
this.canvas.style.cursor = 'grab';
@@ -372,11 +414,11 @@ export class SpectraDisplayer {
return;
}
this.mouseFreq = null;
this.canvas.style.cursor = 'grabbing';
// is dragged
let dx = (mx - lastMouse.x) / this.scaleX;
let dx = (mx - this.lastMouse.x) / this.scaleX;
if (dx != 0) {
// Set the frequency
@@ -399,29 +441,102 @@ export class SpectraDisplayer {
unit: 'Hz'
})
lastMouse = { x: mx, y: my };
this.lastMouse = { x: mx, y: my };
}
});
canvas.addEventListener('mouseup', (e) => {
Utils.on(canvas, 'mouseup touchend', (e) => {
if (!this.enabled) {
let paramsEvent = {
bubbles: e.bubbles,
cancelable: e.cancelable,
clientX: e.clientX,
clientY: e.clientY,
screenX: e.screenX,
screenY: e.screenY,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
button: e.button,
changedTouches: e.changedTouches,
targetTouches: e.targetTouches,
relatedTarget: e.relatedTarget,
};
let touchEvent = new TouchEvent("touchend", paramsEvent);
this.view.catalogCanvas.dispatchEvent(touchEvent);
return;
}
this.isDragging = false;
canvas.style.cursor = 'default';
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: e.clientX,
clientY: e.clientY
});
this.view.catalogCanvas.dispatchEvent(clickEvent);
let mouseXY = Utils.relMouseCoords(e);
const timeDiff = Date.now() - mouseDownTime;
const dx = mouseXY.x - mouseDownPos.x;
const dy = mouseXY.y - mouseDownPos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (timeDiff < CLICK_TIME_THRESHOLD && dist < CLICK_MOVE_THRESHOLD) {
// Custom click detected
const rect = canvas.getBoundingClientRect();
const mx = mouseXY.x;
const my = mouseXY.y;
let v = this.data.values[Math.round(mx / this.scaleX)]
v = this.height - (v - this.minY) * this.scaleY
if (my >= v) {
let dx = (mx - rect.width * 0.5) / this.scaleX;
if (dx != 0) {
// Set the frequency
// look where we are in the freq range
let j = Utils.binarySearch(self.data.freqs, self.data.freq);
let df, f;
if (j > 0 && j < self.data.freqs.length - 1) {
df = (self.data.freqs[j + 1] - self.data.freqs[j - 1]) * 0.5;
f = self.data.freq + dx * df;
} else if (j == 0) {
df = self.data.freqs[1] - self.data.freqs[0]
f = self.data.freqs[0] + dx * df;
} else {
df = self.data.freqs[self.data.freqs.length - 1] - self.data.freqs[self.data.freqs.length - 2];
f = self.data.freqs[self.data.freqs.length - 1] + dx * df;
}
self.hips.setFrequency({
value: f,
unit: 'Hz'
})
}
this.lastMouse = { x: mx, y: my };
}
//this.ctxCursor.clearRect(0, 0, this.width, this.height);
this.mouseFreq = null;
}
if (e.type !== "touchend") {
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: e.clientX,
clientY: e.clientY
});
this.view.catalogCanvas.dispatchEvent(clickEvent);
}
});
canvas.addEventListener('mouseout', (e) => {
Utils.on(canvas, 'mouseout touchcancel', (e) => {
this.isDragging = false;
});
canvas.addEventListener('wheel', (e) => {
Utils.on(canvas, 'wheel', (e) => {
this.ctxCursor.clearRect(0, 0, this.width, this.height);
const wheelEvent = new WheelEvent('wheel', {
@@ -535,7 +650,7 @@ export class SpectraDisplayer {
this._redraw(this.ctx);
}
};
window.addEventListener("spectra", this.spectraUpdateCallback);
this.resetScale();
@@ -550,10 +665,12 @@ export class SpectraDisplayer {
}
enableInteraction() {
this.enabled = true;
this.divNode.style.pointerEvents = "auto"
}
disableInteraction() {
this.enabled = false;
this.divNode.style.pointerEvents = "none"
}
@@ -591,6 +708,16 @@ export class SpectraDisplayer {
this.ctx.lineWidth = 2;
this.ctx.stroke();
this.ctxCursor.clearRect(0, 0, this.width, this.height);
this.ctxCursor.beginPath();
this.ctxCursor.moveTo(this.lastMouse.x, this.height);
let v = this.data.values[Math.round(this.lastMouse.x / this.scaleX)]
v = this.height - (v - this.minY) * this.scaleY
this.ctxCursor.lineTo(this.lastMouse.x, v);
this.ctxCursor.strokeStyle = "yellow";
this.ctxCursor.lineWidth = 2;
this.ctxCursor.stroke()
this._redrawLabels()
}
@@ -703,7 +830,7 @@ export class SpectraDisplayer {
while (i < i1) {
let y;
const x = i * this.scaleX;
let x = i * this.scaleX;
const inValidDomain = this.data.freqIdxStart !== undefined && this.data.freqIdxEnd !== undefined && i >= this.data.freqIdxStart && i <= this.data.freqIdxEnd;
@@ -741,8 +868,6 @@ export class SpectraDisplayer {
y = this.height - (array[i] - this.minY) * this.scaleY;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {

View File

@@ -54,7 +54,8 @@ export let URLBuilder = (function() {
},
buildNEDPositionCSURL: function(ra, dec, radiusDegrees) {
return 'https://ned.ipac.caltech.edu/cgi-bin/nph-objsearch?search_type=Near+Position+Search&of=xml_main&RA=' + ra + '&DEC=' + dec + '&SR=' + radiusDegrees;
//OLD return 'https://ned.ipac.caltech.edu/cgi-bin/nph-objsearch?search_type=Near+Position+Search&of=xml_main&RA=' + ra + '&DEC=' + dec + '&SR=' + radiusDegrees;
return 'https://ned.ipac.caltech.edu/tap/sync?query=SELECT+*+FROM+objdir+WHERE+CONTAINS(POINT(\'J2000\',ra,dec),CIRCLE(\'J2000\',' + ra + ',' + dec + ',' + radiusDegrees + '))=1&LANG=ADQL&REQUEST=doQuery&FORMAT=votable'
},
buildNEDObjectCSURL: function(object, radiusDegrees) {

View File

@@ -384,7 +384,6 @@ Utils.fetch = function(params) {
// localhost url
url = params.url;
}
let request = new Request(url, {
method: params.method || 'GET',

View File

@@ -256,7 +256,7 @@ export let View = (function () {
// some variables for mouse handling
this.dragging = false;
this.dragCoo = null;
this.selectedLayer = 'base';
this.selectedLayer = undefined;
this.needRedraw = true;
@@ -785,17 +785,19 @@ export let View = (function () {
return;
}
view.pinchZoomParameters.isPinching = true;
view.pinchZoomParameters.initialZoomFactor = view.zoomFactor;
view.pinchZoomParameters.initialDistance = Math.sqrt(Math.pow(e.targetTouches[0].clientX - e.targetTouches[1].clientX, 2) + Math.pow(e.targetTouches[0].clientY - e.targetTouches[1].clientY, 2));
view.fingersRotationParameters.initialViewAngleFromCenter = view.wasm.getViewCenter2NorthPoleAngle();
view.fingersRotationParameters.initialViewAngleFromCenter = view.wasm.getRotation();
view.fingersRotationParameters.initialFingerAngle = Math.atan2(e.targetTouches[1].clientY - e.targetTouches[0].clientY, e.targetTouches[1].clientX - e.targetTouches[0].clientX) * 180.0 / Math.PI;
return;
}
view.dragCoo = xymouse;
view.dragPastCoo = xymouse;
view.dragging = true;
@@ -816,39 +818,6 @@ export let View = (function () {
return true;
});
/*
Utils.on(view.catalogCanvas, "mouseup", function (e) {
e.preventDefault();
e.stopPropagation();
const xymouse = Utils.relMouseCoords(e);
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
state: {
mode: view.mode,
dragging: view.dragging,
rightClickPressed: view.rightClick
},
xy: xymouse,
ev: e,
});
if (view.rightClick) {
if (showContextMenu) {
view.aladin.contextMenu && view.aladin.contextMenu.show({e});
}
view.rightClick = false;
return;
}
if (view.mode === View.SELECT) {
view.selector.dispatch('mouseup', {coo: xymouse})
}
});
*/
Utils.on(view.catalogCanvas, "click", function (e) {
// call listener of 'click' event
@@ -875,6 +844,28 @@ export let View = (function () {
});
Utils.on(document, "mouseup touchend", function(e) {
var wasDragging = view.realDragging === true;
if (view.dragging) { // if we were dragging, reset to default cursor
if(view.mode === View.PAN) {
view.setCursor('default');
}
view.dragging = false;
if (wasDragging) {
view.realDragging = false;
// call the positionChanged once more with a dragging = false
view.throttledPositionChanged(false);
}
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
} // end of "if (view.dragging) ... "
});
// reacting on 'click' rather on 'mouseup' is more reliable when panning the view
Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) {
const xymouse = Utils.relMouseCoords(e);
@@ -915,26 +906,27 @@ export let View = (function () {
var wasDragging = view.realDragging === true;
if (view.dragging) { // if we were dragging, reset to default cursor
/*if (view.dragging) { // if we were dragging, reset to default cursor
if(view.mode === View.PAN) {
view.setCursor('default');
}
view.dragging = false;
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
if (wasDragging) {
view.realDragging = false;
// call the positionChanged once more with a dragging = false
view.throttledPositionChanged(false);
}
} // end of "if (view.dragging) ... "
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
} // end of "if (view.dragging) ... "*/
view.mustClearCatalog = true;
view.dragCoo = null;
view.dragPastCoo = null;
if (e.type === "mouseup") {
if (view.mode === View.SELECT) {
@@ -1002,7 +994,9 @@ export let View = (function () {
// TODO : remplacer par mecanisme de listeners
// on avertit les catalogues progressifs
view.refreshProgressiveCats();
view.wasm.releaseLeftButtonMouse();
if (wasDragging) {
view.wasm.releaseLeftButtonMouse();
}
});
var lastHoveredObject; // save last object hovered by mouse
@@ -1249,32 +1243,39 @@ export let View = (function () {
view.realDragging = true;
if (view.mode === View.PAN) {
view.pan = {
s1: view.dragCoo,
s2: xymouse
};
}
var s1 = view.dragCoo, s2 = xymouse;
// update drag coo with the new position
view.dragCoo = xymouse;
// update drag coo with the new position
/*if (view.mode == View.SELECT) {
view.requestRedraw();
return;
}*/
if (view.mode === View.PAN) {
view.wasm.moveMouse(s1.x, s1.y, s2.x, s2.y);
view.wasm.goFromTo(s1.x, s1.y, s2.x, s2.y);
view.updateCenter();
ALEvent.POSITION_CHANGED.dispatchedTo(view.aladin.aladinDiv, view.viewCenter);
// Apply position changed callback after the move
view.throttledPositionChanged(true);
}
}); //// endof mousemove ////
// disable text selection on IE
//Utils.on(view.aladinDiv, "selectstart", function () { return false; })
view.prevWheelTime = undefined;
function normalizeWheel(event) {
// Safari/Chrome on macOS: deltaMode = 0 (pixels), but trackpad steps are tiny
let scale = 1;
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
scale = 16; // assume ~16px per line
} else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
scale = window.innerHeight;
}
return event.deltaY * scale;
}
view.zoomDelta = 0;
Utils.on(view.catalogCanvas, 'wheel', function (e) {
e.preventDefault();
e.stopPropagation();
@@ -1295,24 +1296,13 @@ export let View = (function () {
if (typeof onWheelTriggeredFunction === 'function') {
onWheelTriggeredFunction(e)
} else {
// Default Aladin Lite zooming
view.delta = e.deltaY || e.detail || (-e.wheelDelta);
if (!view.throttledTouchPadZoom) {
view.throttledTouchPadZoom = () => {
const factor = Utils.detectTrackPad(e) ? 1.05 : 1.2;
const currZoomFactor = view.zoom.isZooming ? view.zoom.finalZoom : view.zoomFactor;
let newZoomFactor = view.delta > 0 ? currZoomFactor * factor : currZoomFactor / factor;
// inside case
view.zoom.apply({
stop: newZoomFactor,
duration: 100,
});
};
}
view.throttledTouchPadZoom();
// Default Aladin Lite zooming
const normalizedDelta = e.deltaY && normalizeWheel(e) || e.detail || (-e.wheelDelta);
// Accumulate the normalized delta
// We do not zoom because we cannot rely on "wheel" event
// being triggered at constant time steps
// The zoom is delayed to the redraw which is animation frame requested!
view.zoomDelta += normalizedDelta;
if (view.mode === View.TOOL_COLOR_PICKER) {
pickColor(xymouse);
@@ -1386,17 +1376,44 @@ export let View = (function () {
/**
* redraw the whole view
*/
View.prototype.redraw = function (timestamp) {
// request another frame
requestAnimFrame(this.redrawClbk);
View.prototype.redraw = function (now) {
// Elapsed time since last loop
const now = performance.now();
const elapsedTime = now - timestamp;
this.dt = elapsedTime;
const elapsedTime = now - this.prevTime;
this.prevTime = now;
if (Math.abs(this.zoomDelta) > 1e-3) {
// Apply a fraction each frame (smoothing)
let step = this.zoomDelta * 0.2;
function wheelToZoomFactor(delta) {
const sensitivity = 0.002; // tune this
return Math.exp(-delta * sensitivity);
}
this.zoomFactor /= wheelToZoomFactor(step);
this.zoomDelta -= step;
}
if (this.pan) {
let s1 = this.pan.s1;
let s2 = this.pan.s2;
if (s1 && s2) {
this.wasm.moveMouse(s1.x, s1.y, s2.x, s2.y);
this.wasm.goFromTo(s1.x, s1.y, s2.x, s2.y);
this.updateCenter();
ALEvent.POSITION_CHANGED.dispatchedTo(this.aladin.aladinDiv, this.viewCenter);
// Apply position changed callback after the move
this.throttledPositionChanged(true);
}
this.pan = null;
}
this.moving = this.wasm.update(elapsedTime);
// inertia run throttled position
if (this.moving && this.aladin.callbacksByEventName && this.aladin.callbacksByEventName['positionChanged'] && this.wasm.isInerting()) {
// run the trottled position
@@ -1409,6 +1426,9 @@ export let View = (function () {
this.drawAllOverlays();
}
this.needRedraw = false;
// request another frame
requestAnimFrame(this.redrawClbk);
};
View.prototype.drawAllOverlays = function () {
@@ -1686,6 +1706,10 @@ export let View = (function () {
}
View.prototype.setRotation = function(rotation) {
if (Math.abs(rotation - this.aladin.getRotation()) < 1e-5) {
return;
}
this.wasm.setRotation(rotation);
var rotationChangedCallback = this.aladin.callbacksByEventName["rotationChanged"];
typeof rotationChangedCallback === "function" && rotationChangedCallback(rotation);
@@ -1760,6 +1784,27 @@ export let View = (function () {
// register its promise
this.imageLayersBeingQueried.set(layer, imageLayer);
// Check whether this layer already exist
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layer);
let alreadyPresentImageLayer;
if (idxOverlayLayer == -1) {
// it does not exist so we add it to the stack
this.overlayLayers.push(layer);
} else {
// it exists
alreadyPresentImageLayer = this.imageLayers.get(layer);
if (alreadyPresentImageLayer) {
if (alreadyPresentImageLayer.added === true) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
}
alreadyPresentImageLayer.added = false;
}
// Notify that this image layer has been replaced by the wasm part
this.imageLayers.delete(layer);
}
this.addImageLayer(imageLayer, layer);
return imageLayer;
@@ -1769,33 +1814,13 @@ export let View = (function () {
View.prototype._addLayer = function(imageLayer) {
// Keep the JS frontend in-line with the wasm state
const layerName = imageLayer.layer;
// Check whether this layer already exist
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layerName);
let alreadyPresentImageLayer;
if (idxOverlayLayer == -1) {
// it does not exist so we add it to the stack
this.overlayLayers.push(layerName);
} else {
// it exists
alreadyPresentImageLayer = this.imageLayers.get(layerName);
// Notify that this image layer has been replaced by the wasm part
if (alreadyPresentImageLayer && alreadyPresentImageLayer.added === true) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
}
alreadyPresentImageLayer.added = false;
this.imageLayers.delete(layerName);
}
imageLayer.added = true;
this.imageLayers.set(layerName, imageLayer);
// select the layer if he is on top
//if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
//}
this.selectLayer(layerName);
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
@@ -1853,7 +1878,7 @@ export let View = (function () {
// Remove the settled promise
this.promises.splice(idx, 1);
const noMoreLayersToWaitFor = this.promises.length === 0;
/*const noMoreLayersToWaitFor = this.promises.length === 0;
if (noMoreLayersToWaitFor) {
if (self.empty) {
@@ -1863,48 +1888,19 @@ export let View = (function () {
// it the best I can do if the MOCServer is out
self.aladin.setBaseImageLayer("https://alaskybis.cds.unistra.fr/DSS/DSSColor/");
} else {
// there is surveys that have been queried
// rename the first overlay layer to "base"
self.renameLayer(this.overlayLayers[0], "base");
//self.renameLayer(this.overlayLayers[0], "base");
}
}
}*/
})
}
// The survey at layer must have been added to the view!
View.prototype.renameLayer = function(layer, newLayer) {
if (layer === newLayer) {
return;
}
// Throw an exception if either the first or the second layers are not in the stack
this.wasm.renameLayer(layer, newLayer);
let imageLayer = this.imageLayers.get(layer);
imageLayer.layer = newLayer;
// Change in overlaylayers
const idx = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layer);
this.overlayLayers[idx] = newLayer;
// Change in imageLayers
this.imageLayers.delete(layer);
this.imageLayers.set(newLayer, imageLayer);
if (this.selectedLayer === layer) {
this.selectedLayer = newLayer;
}
// Tell the layer hierarchy has changed
ALEvent.HIPS_LAYER_RENAMED.dispatchedTo(this.aladinDiv, { layer, newLayer });
}
View.prototype.swapLayers = function(firstLayer, secondLayer) {
// Throw an exception if either the first or the second layers are not in the stack
this.wasm.swapLayers(firstLayer, secondLayer);
// Swap in overlaylayers
const idxFirstLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == firstLayer);
const idxSecondLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == secondLayer);
const idxFirstLayer = this.overlayLayers.indexOf(firstLayer);
const idxSecondLayer = this.overlayLayers.indexOf(secondLayer);
const tmp = this.overlayLayers[idxFirstLayer];
this.overlayLayers[idxFirstLayer] = this.overlayLayers[idxSecondLayer];
@@ -1943,7 +1939,7 @@ export let View = (function () {
this.overlayLayers.splice(idxOverlaidLayer, 1);
if (this.overlayLayers.length === 0) {
this.empty = true;
//this.empty = true;
} else if (this.selectedLayer === layer) {
// If the layer removed was selected then we select the last layer
this.selectLayer(this.overlayLayers[this.overlayLayers.length - 1]);
@@ -1952,12 +1948,12 @@ export let View = (function () {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
// check if there are no more surveys
const noMoreLayersToWaitFor = this.promises.length === 0;
/*const noMoreLayersToWaitFor = this.promises.length === 0;
if (noMoreLayersToWaitFor && this.empty) {
// no promises to launch!
const dssId = Aladin.DEFAULT_OPTIONS.survey;
this.aladin.setBaseImageLayer(dssId);
}
}*/
};
View.prototype.contains = function(survey) {
@@ -2004,7 +2000,8 @@ export let View = (function () {
}
}
View.prototype.getImageLayer = function (layer = "base") {
View.prototype.getImageLayer = function (layer) {
layer = layer || (this.overlayLayers && this.overlayLayers[0]);
let imageLayerQueried = this.imageLayersBeingQueried.get(layer);
let imageLayer = this.imageLayers.get(layer);

View File

@@ -33,6 +33,8 @@ import { Utils } from "../../Utils.ts";
import { ActionButton } from "../Widgets/ActionButton.js";
import infoIconUrl from "../../../../assets/icons/info.svg"
import { Icon } from "../Widgets/Icon.js";
import { Tree } from "../Widgets/Tree.js";
import { ALEvent } from "../../events/ALEvent.js";
/******************************************************************************
* Aladin Lite project
@@ -44,27 +46,119 @@ import { Icon } from "../Widgets/Icon.js";
*
*****************************************************************************/
function fillHiPSHierarchy(name, hips, path, hierarchy) {
let folders = path.split('/')
let curFolder = folders.shift()
if(curFolder === 'Image') {
let newPath = folders.join('/')
fillHiPSHierarchy(name, hips, newPath, hierarchy);
} else {
// Some exceptions because the MOCServer client_category field may contain some typos
if (['X', 'X-ray', 'Xray'].includes(curFolder)) {
curFolder = 'X-ray'
}
if (['Radion', 'Radio'].includes(curFolder)) {
curFolder = 'Radio'
}
if (curFolder === "Deprecated")
return;
hierarchy[curFolder] = hierarchy[curFolder] || {};
if (folders.length == 0) {
hierarchy[curFolder][name] = hips
} else {
let newPath = folders.join('/')
fillHiPSHierarchy(name, hips, newPath, hierarchy[curFolder])
}
}
}
export class HiPSBrowserBox extends Box {
static HiPSList = {};
constructor(aladin, options) {
let self;
let filter = (item, params) => {
if (params.regime) {
if (!item.obs_regime)
return false;
if (params.regime.toLowerCase() !== item.obs_regime.toLowerCase()) {
return false;
}
}
if (params.resolution) {
if (!item.hips_tile_width || !item.hips_order) {
return false;
}
let pixelHEALPixOrder = Math.log2(item.hips_tile_width) + (+item.hips_order);
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
if (resPixel > params.resolution)
return false;
}
if (params.title) {
if (!item.obs_title)
return false;
if (!item.obs_title.toLowerCase().includes(params.title.toLowerCase())) {
return false;
}
}
return true;
};
// Search tree
let searchTree = new Tree({
// a JS object describing the tree to show
label: (item) => {
let name = item.obs_title.replace(/:|\'/g, '');
return name;
},
// a callback called when the user selects a leaf item of the tree
click: (item) => {
let image = item.ID || item.hips_service_url;
let name = item.obs_title || item.ID;
self._addHiPS(image, name)
},
// a callback called for filtering
filter,
});
MocServer.getAllHiPSes().then((HiPSes) => {
HiPSBrowserBox.HiPSList = {}
let hipsHierarchy = {};
// Fill the HiPSList from the MOCServer
// Build a hierarchy w.r.t sorted by regime
HiPSes.forEach((h) => {
let name = h.obs_title;
name = name.replace(/:|\'/g, '');
HiPSBrowserBox.HiPSList[name] = h;
if (h.client_category) {
let path = h.client_category
fillHiPSHierarchy(name, h, path, hipsHierarchy)
}
});
self.searchTree.setHierarchy(hipsHierarchy)
// Initialize the autocompletion without any filtering
self._filterHiPSList({})
});
const _parseHiPS = (e) => {
const value = e.target.value;
@@ -88,7 +182,6 @@ export class HiPSBrowserBox extends Box {
if (image) {
self._addHiPS(image, name)
self.searchDropdown.update({title: value});
}
};
@@ -118,19 +211,17 @@ export class HiPSBrowserBox extends Box {
disable: true,
})
self.searchTree.triggerFilter({title: e.target.value});
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
change(e) {
e.stopPropagation();
e.preventDefault()
_parseHiPS(e)
}
},
});
let filterEnabler = Input.checkbox({
name: "filter-enabler",
tooltip: { content: "enable/disable" },
checked: false,
click(e) {
let on = e.target.checked;
@@ -182,13 +273,9 @@ export class HiPSBrowserBox extends Box {
},
toggled: false,
actionOn: (e) => {
self.filterBox._show({
position: {
nextTo: filterBtn,
direction: "bottom",
aladin,
},
});
self.filterBox._show({position: {
anchor: 'right center'
}});
},
actionOff: (e) => {
self.filterBox._hide();
@@ -205,15 +292,17 @@ export class HiPSBrowserBox extends Box {
size: 'medium',
url: hipsIconUrl,
monochrome: true,
}), "HiPS browser"])
},
onDragged: () => {
if (self.filterBtn.toggled) {
self.filterBtn.toggle();
}
}), "HiPS browser"]),
draggable: true,
},
//onDragged: () => {
//if (self.filterBtn.toggled) {
//self.filterBtn.toggle();
//}
//},
classList: ['aladin-HiPS-browser-box'],
content: Layout.vertical([
searchTree,
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn, filterNumberElt])]),
]),
@@ -222,6 +311,9 @@ export class HiPSBrowserBox extends Box {
aladin.aladinDiv
);
self = this;
this.searchTree = searchTree;
this.filterBox = new HiPSFilterBox(aladin, {
callback: (params) => {
self._filterHiPSList(params);
@@ -236,46 +328,42 @@ export class HiPSBrowserBox extends Box {
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
self = this;
this.filter = filter;
this.filterCallback = (HiPS, params) => {
if (params.regime) {
if (!HiPS.obs_regime)
return false;
filterEnabler.action({target: {checked: true}});
if (params.regime.toLowerCase() !== HiPS.obs_regime.toLowerCase()) {
return false;
}
}
this._addListeners();
if (params.spatial) {
if (!HiPS.ID)
return false;
this._requestMOCServer();
}
if (Array.isArray(params.spatial) && !(params.spatial.includes(HiPS.ID))) {
return false;
}
}
_addListeners() {
const requestMOCServerDebounced = Utils.debounce(() => {
this._requestMOCServer()
}, 500);
if (params.resolution) {
if (!HiPS.hips_tile_width || !HiPS.hips_order) {
return false;
}
ALEvent.POSITION_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
ALEvent.ZOOM_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
}
let pixelHEALPixOrder = Math.log2(HiPS.hips_tile_width) + (+HiPS.hips_order);
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
_requestMOCServer() {
if (this.isHidden && this.searchTree) {
return;
}
if (resPixel > params.resolution)
return false;
}
return true;
};
let self = this;
MocServer.getAllHiPSesInsideView(this.aladin)
.then((HiPSes) => {
let HiPSIDs = HiPSes.map((x) => x.ID);
self.searchTree.highlightNodes(HiPSIDs)
})
}
_addHiPS(id, name) {
let self = this;
self.searchDropdown.update({value: name, title: name});
let hips = A.imageHiPS(id, {
name,
successCallback: (hips) => {
@@ -401,8 +489,8 @@ export class HiPSBrowserBox extends Box {
let HiPS = HiPSBrowserBox.HiPSList[key];
// apply filtering
if (
self.filterCallback &&
self.filterCallback(HiPS, params)
self.filter &&
self.filter(HiPS, params)
) {
// search with the name or id
let name = HiPS.obs_title;
@@ -412,14 +500,15 @@ export class HiPSBrowserBox extends Box {
}
}
if (self.searchTree) {
self.searchTree.triggerFilter(params);
}
self.searchDropdown.update({ options: HiPSIDs });
self.filterNumberElt.innerHTML = HiPSIDs.length + "/" + Object.keys(HiPSBrowserBox.HiPSList).length;
}
_hide() {
if (this.filterBox)
this.filterBox.signalBrowserStatus(true)
if (this.filterBtn && this.filterBtn.toggled) {
this.filterBtn.toggle();
}
@@ -428,12 +517,10 @@ export class HiPSBrowserBox extends Box {
}
_show(options) {
this._requestMOCServer();
// Regenerate a new layer name
this.layer = Utils.uuidv4()
if (this.filterBox)
this.filterBox.signalBrowserStatus(false)
this.layer = (options && options.layer) || Utils.uuidv4();
super._show(options)
}
}

View File

@@ -19,17 +19,11 @@
import { Box } from "../Widgets/Box.js";
import { Form } from "../Widgets/Form.js";
import { MocServer } from "../../MocServer.js";
import { TogglerActionButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
import { Angle } from "../../libs/astro/angle.js";
import { ALEvent } from "../../events/ALEvent.js";
import { Utils } from "../../Utils.ts";
import { AladinUtils } from "../../AladinUtils.js";
import { Input } from "../Widgets/Input.js";
import freqIconUrl from '../../../../assets/icons/freq.svg';
import inViewIconUrl from '../../../../assets/icons/inside.svg';
import targetIconUrl from '../../../../assets/icons/target.svg';
/******************************************************************************
* Aladin Lite project
@@ -45,120 +39,96 @@ export class HiPSFilterBox extends Box {
constructor(aladin, options) {
let self;
let regimeBtn = new TogglerActionButton({
content: 'Freq',
icon: {
monochrome: true,
size: 'medium',
url: freqIconUrl,
},
tooltip: {content: 'Observation bandwidth', position: {direction: 'bottom'}},
toggled: true,
actionOn: () => {
self._triggerFilteringCallback();
},
actionOff: () => {
let regimeBtn = Input.checkbox({
name: 'Freq',
tooltip: {content: 'Observation bandwidth', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
self._triggerFilteringCallback();
}
});
let spatialBtn = new TogglerActionButton({
content: 'In view',
icon: {
monochrome: true,
size: 'medium',
url: inViewIconUrl,
},
tooltip: {content: 'Survey in view only!', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._requestMOCServer();
},
actionOff: () => {
self._triggerFilteringCallback();
}
});
let resolutionBtn = new TogglerActionButton({
content: 'Resolution',
icon: {
monochrome: true,
size: 'medium',
url: targetIconUrl,
},
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
},
actionOff: () => {
let resolutionBtn = Input.checkbox({
name: 'Resolution',
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
self._triggerFilteringCallback();
}
});
let logSlider = new Input({
let regimeOption = Layout.horizontal({
tooltip: {
content: "Observation regime",
position: { direction: "right" },
},
label: 'Freq: ',
layout: [Input.select({
value: "Optical",
options: [
"Radio",
"Infrared",
"Millimeter",
"Optical",
"UV",
"EUV",
"X-ray",
"Gamma-ray",
],
change: (e) => {
let regime = e.target.value;
self.params["regime"] = regime;
self._triggerFilteringCallback();
},
}), regimeBtn]
});
let resolutionOption = Layout.horizontal({
label: "Max resolution [°/px]:",
name: "res",
value: 0.1,
type: 'range',
cssStyle: {
width: '100%'
},
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
stretch: "log",
min: 0.1 / 3600,
max: 0.1,
reversed: true,
change: (e, slider, deg) => {
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
layout: [
new Input({
name: "res",
value: 0.1,
type: 'range',
cssStyle: {
width: '200px'
},
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
stretch: "log",
min: 0.1 / 3600,
max: 0.1,
reversed: true,
change: (e, slider, deg) => {
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
let resolution = new Angle(deg);
self.params["resolution"] = resolution.degrees();
let resolution = new Angle(deg);
self.params["resolution"] = resolution.degrees();
self._triggerFilteringCallback();
},
self._triggerFilteringCallback();
},
}),
resolutionBtn,
]
});
super(
{
classList: ['aladin-HiPS-filter-box'],
header: {
title: 'Filter tags',
draggable: false,
},
close: false,
classList: ['aladin-HiPS-filter-box'],
content: Layout.vertical([
'<b>Filter by:</b>',
Layout.horizontal([regimeBtn, spatialBtn, resolutionBtn]),
'<b>Details:</b>',
new Form({
subInputs: [
{
type: "group",
subInputs: [
{
label: "Freq:",
name: "regime",
value: "Optical",
type: 'select',
options: [
"Radio",
"Infrared",
"Millimeter",
"Optical",
"UV",
"EUV",
"X-ray",
"Gamma-ray",
],
change: (e) => {
let regime = e.target.value;
self.params["regime"] = regime;
//regimeBtn.update({content: regime});
self._triggerFilteringCallback();
},
tooltip: {
content: "Observation regime",
position: { direction: "right" },
},
},
logSlider
regimeOption,
resolutionOption
],
},
],
@@ -170,60 +140,28 @@ export class HiPSFilterBox extends Box {
self = this;
this.browserClosed = false;
this.callback = options.callback;
this.regimeBtn = regimeBtn;
this.spatialBtn = spatialBtn;
this.resolutionBtn = resolutionBtn;
this.params = {
regime: "Optical",
spatial: true,
highlight: true,
resolution: 1, // 1°/pixel
};
this.on = false;
this.aladin = aladin;
this._addListeners();
}
_addListeners() {
const requestMOCServerDebounced = Utils.debounce(() => {
this._requestMOCServer()
}, 500);
ALEvent.POSITION_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
ALEvent.ZOOM_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
}
_requestMOCServer() {
if (!this.spatialBtn.toggled || !this.on || this.browserClosed) {
return;
}
let self = this;
MocServer.getAllHiPSesInsideView(this.aladin)
.then((HiPSes) => {
let HiPSIDs = HiPSes.map((x) => x.ID);
self.params["spatial"] = HiPSIDs;
self._triggerFilteringCallback();
})
}
_triggerFilteringCallback() {
let filterParams = {};
if (this.regimeBtn.toggled) {
if (this.regimeBtn.checked) {
filterParams['regime'] = this.params['regime']
}
if (this.spatialBtn.toggled) {
filterParams['spatial'] = this.params['spatial']
}
if (this.resolutionBtn.toggled) {
if (this.resolutionBtn.checked) {
filterParams['resolution'] = this.params['resolution']
}
@@ -232,21 +170,18 @@ export class HiPSFilterBox extends Box {
}
}
signalBrowserStatus(closed) {
/*signalBrowserStatus(closed) {
this.browserClosed = closed;
// open
if (!closed) {
this._requestMOCServer()
}
}
}*/
enable(enable) {
this.on = enable;
if (this.on)
this._requestMOCServer();
this._triggerFilteringCallback();
}
}

View File

@@ -219,11 +219,6 @@ import { TogglerActionButton } from "../Button/Toggler.js";
value: 0.0,
change: (e) => {
let minCut = +e.target.value
let imgFormat = self.options.layer.imgFormat;
if (imgFormat !== "fits") {
minCut /= 255.0;
}
self.options.layer.setCuts(minCut, self.options.layer.getColorCfg().getCuts()[1])
}
},
@@ -238,12 +233,6 @@ import { TogglerActionButton } from "../Button/Toggler.js";
value: 1.0,
change: (e) => {
let maxCut = +e.target.value
let imgFormat = self.options.layer.imgFormat;
if (imgFormat !== "fits") {
maxCut /= 255.0;
}
self.options.layer.setCuts(self.options.layer.getColorCfg().getCuts()[0], maxCut)
}
}]
@@ -251,7 +240,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
let colorSettingsContent = new Form({
subInputs: [{
label: 'colormap:',
label: 'cmap:',
type: 'select',
name: 'cmap',
value: 'native',
@@ -302,21 +291,23 @@ import { TogglerActionButton } from "../Button/Toggler.js";
let reversed = colorCfg.getReversed();
let [minCut, maxCut] = colorCfg.getCuts();
if (layer.imgFormat !== "fits") {
minCut = Math.round(minCut * 255);
maxCut = Math.round(maxCut * 255);
}
this.pixelSettingsContent.set('mincut', +minCut.toFixed(4))
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
if (minCut)
this.pixelSettingsContent.set('mincut', +minCut.toFixed(4))
if (maxCut)
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
this.pixelSettingsContent.set('stretch', stretch)
let fmtInput = this.pixelSettingsContent.getInput('fmt')
fmtInput.innerHTML = '';
for (const option of layer.getAvailableFormats()) {
fmtInput.innerHTML += "<option>" + option + "</option>";
if (layer.getAvailableFormats()) {
for (const option of layer.getAvailableFormats()) {
fmtInput.innerHTML += "<option>" + option + "</option>";
}
fmtInput.value = layer.imgFormat;
}
fmtInput.value = layer.imgFormat;
this.colorSettingsContent.set('cmap', colormap);
this.colorSettingsContent.set('reverse', reversed);
@@ -331,7 +322,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
update(options) {
if (options.layer) {
let self = this;
if (options.layer.isSpectralCube()) {
if (options.layer.isSpectralCube && options.layer.isSpectralCube()) {
let spectraDisplayer = self.aladin.view.spectraDisplayer;
self.spectraBtn = new TogglerActionButton({

View File

@@ -43,8 +43,7 @@ import removeIconUrl from "../../../../assets/icons/remove.svg";
import settingsIconUrl from "../../../../assets/icons/settings.svg";
import searchIconImg from "../../../../assets/icons/search.svg";
import downloadIconUrl from '../../../../assets/icons/download.svg';
import swapIcon from '../../../../assets/icons/swap.svg'
import { TogglerActionButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { Box } from "../Widgets/Box.js";
@@ -75,7 +74,7 @@ export class OverlayStackBox extends Box {
};*/
static predefinedCats = {
simbad: {
url: "https://axel.u-strasbg.fr/HiPSCatService/SIMBAD",
url: "https://axel.cds.unistra.fr/HiPSCatService/SIMBAD",
options: {
id: "simbad",
name: "SIMBAD",
@@ -98,7 +97,7 @@ export class OverlayStackBox extends Box {
},
},
gaia: {
url: "https://axel.u-strasbg.fr/HiPSCatService/I/355/gaiadr3",
url: "https://axel.cds.unistra.fr/HiPSCatService/I/355/gaiadr3",
options: {
id: "gaia-dr3",
name: "Gaia DR3",
@@ -109,7 +108,7 @@ export class OverlayStackBox extends Box {
},
},
twomass: {
url: "https://axel.u-strasbg.fr/HiPSCatService/II/246/out",
url: "https://axel.cds.unistra.fr/HiPSCatService/II/246/out",
options: {
id: "2mass",
name: "2MASS",
@@ -731,6 +730,8 @@ export class OverlayStackBox extends Box {
// one must add the current HiPS too!
favoritesCopy.sort();
favoritesCopy.push("More...")
hips.HiPSSelector.update({value: currentHiPS, options: favoritesCopy});
}
});
@@ -745,14 +746,6 @@ export class OverlayStackBox extends Box {
}
}
/*if (this.hipsBrowser) {
this.hipsBrowser._hide();
}*/
/*if (this.catBox) {
this.catBox._hide();
}*/
if (this.addOverlayBtn) this.addOverlayBtn.hideMenu();
if (this.addHiPSBtn) this.addHiPSBtn.hideMenu();
@@ -781,7 +774,6 @@ export class OverlayStackBox extends Box {
})
);
layout = layout.concat(this._createSurveysList());
return Layout.vertical({ layout });
}
@@ -866,9 +858,7 @@ export class OverlayStackBox extends Box {
let item = Layout.horizontal({
layout: [
this._addOverlayIcon(overlay),
'<div style="background-color: rgba(0, 0, 0, 0.6); padding: 3px; border-radius: 3px; word-break: break-word;">' +
name +
"</div>",
name,
Layout.horizontal({ layout: optBtn }),
],
cssStyle: {
@@ -923,6 +913,9 @@ export class OverlayStackBox extends Box {
hipsOptions.sort()
for (const layer of layers) {
if (!layer) {
continue;
}
let options = Array.from([...hipsOptions])
let value = layer.name || layer.id
@@ -930,6 +923,7 @@ export class OverlayStackBox extends Box {
options.push(value)
}
options.push("More...")
let HiPSSelector = Input.select({
value,
@@ -937,6 +931,14 @@ export class OverlayStackBox extends Box {
title: layer.name,
change: (e) => {
let name = e.target.value;
if (name === "More...") {
if (!self.hipsBrowser)
self.hipsBrowser = new HiPSBrowserBox(self.aladin);
self.hipsBrowser._show({ layer: layer.layer, position: { anchor: "center center" } });
return;
}
// search for the
let overlayLayer;
if (name in self.cachedHiPS) {
@@ -954,8 +956,7 @@ export class OverlayStackBox extends Box {
let deleteBtn = ActionButton.createSmallSizedIconBtn({
icon: { url: removeIconUrl, monochrome: true },
disable: layer.layer === "base",
//disable: layer.layer === "base",
tooltip: { content: "Remove", position: { direction: "top" } },
action(e) {
self.aladin.removeImageLayer(layer.layer);
@@ -964,6 +965,7 @@ export class OverlayStackBox extends Box {
},
});
let prevOpacity = null;
let showBtn = ActionButton.createSmallSizedIconBtn({
icon: {
url: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
@@ -979,12 +981,15 @@ export class OverlayStackBox extends Box {
let opacity = layer.getOpacity();
if (opacity === 0.0) {
layer.setOpacity(1.0);
let newOpacity = prevOpacity || 1.0;
prevOpacity = null;
layer.setOpacity(newOpacity);
btn.update({
icon: { monochrome: true, url: showIconUrl },
tooltip: { content: "Hide" },
});
} else {
prevOpacity = opacity;
layer.setOpacity(0.0);
btn.update({
icon: { monochrome: true, url: hideIconUrl },
@@ -1105,12 +1110,45 @@ export class OverlayStackBox extends Box {
},
});
self.layer2swap = null;
let swapBtn = new ActionButton({
size: "small",
icon: {
url: swapIcon,
size: "small",
monochrome: true,
},
tooltip: {
content: "Swap 2 layers",
position: { direction: "top" },
},
toggled: false,
action: (_) => {
let toggled = swapBtn.options.toggled;
if (!toggled) {
if (!self.layer2swap) {
self.layer2swap = layer;
} else {
self.aladin.view.swapLayers(self.layer2swap.layer, layer.layer);
}
} else {
if (self.layer2swap) {
self.layer2swap = null;
}
}
swapBtn.update({
toggled: !toggled,
});
},
});
let btns = [showBtn, settingsBtn];
if (!(layer instanceof Image)) {
btns.push(loadMOCBtn);
}
btns.push(deleteBtn);
btns = btns.concat([swapBtn, deleteBtn]);
let item = Layout.horizontal({
layout: [HiPSSelector, Layout.horizontal(btns)],

View File

@@ -1,70 +0,0 @@
// Copyright 2023 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { ALEvent } from "../../events/ALEvent";
import { ActionButton } from "../Widgets/ActionButton";
import mapIconUrl from '../../../../assets/icons/map.svg';
/******************************************************************************
* Aladin Lite project
*
* File gui/ActionButton.js
*
* A context menu that shows when the user right clicks, or long touch on touch device
*
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
export class MainSurveyActionButton extends ActionButton {
/**
* UI responsible for displaying the viewport infos
* @param {Aladin} aladin - The aladin instance.
*/
constructor(aladin, options) {
super({
...options,
tooltip: {content: 'Survey name<br/>Click to change it!', position: { direction: 'bottom' }},
iconURL: mapIconUrl,
})
this.addClass('medium-sized-icon')
this._addListeners(aladin)
}
_addListeners(aladin) {
ALEvent.HIPS_LAYER_ADDED.listenedBy(aladin.aladinDiv, (e) => {
const layer = e.detail.layer;
if (layer.layer === 'base') {
let name = (layer.properties && layer.properties.obsTitle) || layer.name;
this.update({
tooltip: {
content: 'Survey: ' + name,
position: {
direction: 'left'
}
}
})
}
});
}
}

View File

@@ -183,6 +183,17 @@ export class SettingsCtxMenu extends ContextMenu {
content: [self.backgroundColorInput, 'Back color']
},
},
{
label: {
content: 'Light/Dark mode'
},
action(o) {
const currentTheme = self.aladin.aladinDiv.getAttribute("data-theme");
const newTheme = currentTheme === "dark" ? "light" : "dark";
self.aladin.aladinDiv.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
}
},
{
label: {
content: [self.hpxGridCheckbox, 'HEALPix grid']

View File

@@ -79,7 +79,7 @@ export class Dropdown extends Input {
super({
type: 'text',
autocomplete: {options: options.options},
//autocomplete: {options: options.options},
...options
})
this.el.classList.add('search')
@@ -91,11 +91,11 @@ export class Dropdown extends Input {
update(options) {
let newOptions = {};
if (options && options.options) {
/*if (options && options.options) {
newOptions['autocomplete'] = {options: options.options};
delete options.options;
}
}*/
// add the other input text options
newOptions = {...newOptions, ...options};

View File

@@ -67,6 +67,47 @@ export class Layout extends DOMElement {
this.appendContent(item)
}
}
if (options.draggable) {
// retrieve the children and add the drag listeners
let draggableFn = options.draggable;
let firstSelected = null;
this.el.childNodes.forEach(div => {
div.addEventListener("click", () => {
// If nothing selected yet → select this one
if (!firstSelected) {
firstSelected = div;
div.classList.add("aladin-item-selected");
return;
}
// If clicking the same one again → unselect
if (firstSelected === div) {
div.classList.remove("aladin-item-selected");
firstSelected = null;
return;
}
// Otherwise: swap the two elements
let a = firstSelected;
let b = div;
let temp = document.createElement("div");
a.parentNode.insertBefore(temp, a);
b.parentNode.insertBefore(a, b);
temp.parentNode.insertBefore(b, temp);
temp.remove();
// Exec callback
draggableFn(a, b)
// Clear selection
a.classList.remove("aladin-item-selected");
firstSelected = null;
});
});
}
}
// The tooltip has to be set once the element
@@ -182,9 +223,5 @@ export class Layout extends DOMElement {
if (this.options.position) {
this.setPosition(this.options.position)
}
//super._show()
// attach to the DOM again
//this.attachTo(this.target);
}
}
}

View File

@@ -225,6 +225,9 @@ export class Location extends DOMElement {
// lon and lat must be given in cooFrame
const updateFromLonLatFunc = (lon, lat, cooFrame) => {
var coo = new Coo(lon, lat, Location.prec);
cooFrame = CooFrameEnum.fromString(cooFrame);
if (cooFrame == CooFrameEnum.ICRS) {
self.field.set(coo.format('s/'));
}
@@ -237,7 +240,7 @@ export class Location extends DOMElement {
self.field.removeClass('aladin-not-valid');
self.field.removeClass('aladin-valid');
self.field.element().style.color = options.center ? 'var(--aladin-color)' : 'white';
self.field.element().style.color = options.center ? 'var(--aladin-color)' : 'var(--text-color)';
};
if (options.ra && options.dec) {

View File

@@ -137,11 +137,7 @@ export class Box extends DOMElement {
if (this.options.content) {
let content = this.options.content
//if (Array.isArray(content)) {
this.appendContent(content);
//} else {
// this.appendContent(content);
//}
this.appendContent(content);
}
if (this.options.position) {

View File

@@ -74,12 +74,17 @@ export class Input extends DOMElement {
this.el.type = this.type;
this.el.checked = this.options.checked;
this.checked = this.options.checked;
// for checkbox widgets, we authorize calling the callback name click or change
let action = this.options.click || this.options.change;
if (action) {
this.el.removeEventListener('click', this.action);
this.action = action;
this.action = (e) => {
this.checked = this.el.checked;
action(e);
};
this.el.addEventListener('click', this.action);
}
} else if (this.type === "select") {
@@ -374,6 +379,7 @@ export class Input extends DOMElement {
set(value) {
if (this.el.type === "checkbox") {
this.el.checked = value;
this.checked = value;
} else {
this.el.value = value;
}

331
src/js/gui/Widgets/Tree.js Normal file
View File

@@ -0,0 +1,331 @@
// Copyright 2023 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { DOMElement } from "./Widget";
import { Icon } from "./Icon";
import folderIconUrl from "../../../../assets/icons/folder.svg";
/******************************************************************************
* Aladin Lite project
*
* File gui/Tree.js
*
* A tree
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************
*/
export class Tree extends DOMElement {
constructor(options, target, position = "beforeend") {
let el = document.createElement("div");
el.classList.add('aladin-tree');
super(el, options);
this.click = options && options.click;
let rootNode = options && options.root || {};
this.params = null;
this.filter = options && options.filter;
this.label = options && options.label;
this._setRoot(rootNode);
this.attachTo(target, position);
this._show();
this.addClass('aladin-dark-theme')
}
_setRoot(root) {
if (root) {
root.label = new Icon({
size: "small",
url: folderIconUrl,
monochrome: true,
cssStyle: {'display': 'inline-block'}
}).element().outerHTML;
}
this.root = root;
this._createDOM(root);
}
_createDOM(node) {
if (!node) {
return;
}
this.el.innerHTML = "";
this.curNode = node;
// create the parent directory
let curNode = this.curNode;
let directoryLinks = []
var levelsOfParenty = 0;
while (curNode) {
let parentLinkEl = document.createElement('a');
parentLinkEl.innerHTML = curNode.label;
let curLevel = levelsOfParenty;
parentLinkEl.addEventListener('click', () => {
this.navigate(curLevel)
})
levelsOfParenty += 1;
parentLinkEl.classList.add("aladin-link");
directoryLinks.push(parentLinkEl);
curNode = curNode.parent;
}
directoryLinks.reverse()
let directoryListEl = document.createElement('div');
directoryListEl.classList.add('aladin-directory-path');
directoryListEl.style.display = "inline-block"
for (var link of directoryLinks) {
directoryListEl.appendChild(link);
// root node
let spanSplitEl = document.createElement('span')
spanSplitEl.innerText = ' \/ '
directoryListEl.appendChild(spanSplitEl)
}
this.el.appendChild(directoryListEl)
let listElt = document.createElement('ul');
for (const label of Object.keys(node).sort()) {
if (label !== 'parent' && label !== "label") {
let elt = document.createElement('li');
// points towards the parent node
let child = node[label];
let isLeaf = typeof child === "object" && 'ID' in child;
if (isLeaf) {
if(this.params && this.filter && !this.filter(child, this.params)) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
elt.innerHTML = this.label(child);
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
let numFilteringMatching = this.numChildMatchingFilter(child, true);
let numTotal = this.numChildMatchingFilter(child, false);
let name = label;
elt.innerHTML = name + ` (${numFilteringMatching}/${numTotal})`
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
}
elt.style.color = this.hasChildLocatedInFov(child) ? 'yellowgreen' : 'orange';
child.label = label;
child.parent = node;
elt.for = label;
elt.classList.add("aladin-link");
elt.addEventListener('click', (e) => {
if (isLeaf) {
this.click(child)
} else {
// not leaf
this._createDOM(child);
}
})
listElt.appendChild(elt)
}
}
this.el.appendChild(listElt);
}
setHierarchy(root) {
this._setRoot(root)
}
navigate(numOfLevels) {
let curNode = this.curNode;
while (curNode && curNode.parent && numOfLevels >= 1) {
numOfLevels -= 1;
curNode = curNode.parent;
}
this._createDOM(curNode)
}
highlightNodes(highlight) {
this.highlight = highlight
let elts = this.el.querySelectorAll("li");
let i = 0;
for (const label of Object.keys(this.curNode).sort()) {
if (label !== 'parent' && label !== "label") {
let elt = elts[i];
i += 1;
// points towards the parent node
let child = this.curNode[label];
let isLeaf = typeof child === "object" && 'ID' in child;
if (isLeaf) {
// Check if its ID is found in the view
if (this.highlight) {
elt.style.color = this.highlight.includes(child.ID) ? 'yellowgreen' : 'orange';
}
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
elt.style.color = this.hasChildLocatedInFov(child) ? 'yellowgreen' : 'orange';
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
let numFilteringMatching = this.numChildMatchingFilter(child, true);
let numTotal = this.numChildMatchingFilter(child, false);
let name = elt.innerText.split('(');
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
}
}
}
}
// Set params to null, undefined or {} to disable the filtering
triggerFilter(params) {
if (params && params.title) {
params.title = params.title.toLowerCase()
}
this.params = params;
let elts = this.el.querySelectorAll("li");
let i = 0;
for (const label of Object.keys(this.curNode).sort()) {
if (label !== 'parent' && label !== "label") {
let elt = elts[i];
i += 1;
// points towards the parent node
let child = this.curNode[label];
let isLeaf = typeof child === "object" && 'ID' in child;
if (isLeaf) {
if(this.params && this.filter && !this.filter(child, this.params)) {
elt.style.display = "none"
} else {
elt.style.display = "block"
}
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
let numFilteringMatching = this.numChildMatchingFilter(child, true);
let numTotal = this.numChildMatchingFilter(child, false);
let name = elt.innerText.split('(');
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
}
}
}
}
hasChildLocatedInFov(node) {
if (!this.highlight) {
return false;
}
if (typeof node !== "object") {
return false;
}
let isLeaf = typeof node === "object" && 'ID' in node;
if (isLeaf) {
if (this.highlight.includes(node.ID)) {
return true;
}
} else {
for (const label of Object.keys(node).sort()) {
if (label === "parent")
continue;
let child = node[label];
if (child && this.hasChildLocatedInFov(child)) {
return true;
}
}
}
return false;
}
numChildMatchingFilter(node, filtering) {
if (typeof node !== "object") {
return 0;
}
let isLeaf = typeof node === "object" && 'ID' in node;
if (isLeaf) {
if (!filtering || (this.params && this.filter && this.filter(node, this.params))) {
return 1;
} else {
return 0;
}
} else {
let num = 0;
for (const label of Object.keys(node).sort()) {
if (label === "parent")
continue;
let child = node[label];
if (child) {
num += this.numChildMatchingFilter(child, filtering);
}
}
return num;
}
}
}

View File

@@ -47,6 +47,28 @@ export class DOMElement {
this.options = options;
this.name = options && options.name || Utils.uuidv4()
this.isHidden = true;
/*this.el.addEventListener("mouseup", (e) => {
var wasDragging = view.realDragging === true;
if (view.dragging) { // if we were dragging, reset to default cursor
if(view.mode === View.PAN) {
view.setCursor('default');
}
view.dragging = false;
if (wasDragging) {
view.realDragging = false;
// call the positionChanged once more with a dragging = false
view.throttledPositionChanged(false);
}
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
}
});*/
}
element() {

View File

@@ -270,7 +270,7 @@ export let Ellipse = (function() {
if (! baseColor) {
baseColor = '#ff0000';
}
if (this.isSelected) {
if(this.selectionColor) {
ctx.strokeStyle = this.selectionColor;
@@ -318,8 +318,6 @@ export let Ellipse = (function() {
let [xb, yb] = getVertexOnEllipse(3 * Math.PI * 0.5)
let [xc, yc] = getVertexOnEllipse(Math.PI)
let [xd, yd] = getVertexOnEllipse(0)
ctx.save();
ctx.lineWidth = Math.max(this.lineWidth * 0.5, 1.0);
ctx.setLineDash([this.lineWidth, this.lineWidth]);
@@ -329,8 +327,6 @@ export let Ellipse = (function() {
ctx.lineTo(xd, yd);
ctx.stroke();
ctx.restore()
}
}

View File

@@ -61,16 +61,9 @@ export let Polyline = (function() {
return mag2;
}
function _drawLine(l, ctx, noStroke) {
noStroke = noStroke===true || false;
ctx.beginPath();
function _drawLine(l, ctx) {
ctx.moveTo(l.x1, l.y1);
ctx.lineTo(l.x2, l.y2);
if (!noStroke) {
ctx.stroke();
}
}
/**
@@ -232,7 +225,6 @@ export let Polyline = (function() {
return false;
}
noSmallCheck = noSmallCheck===true || false;
noStroke = noStroke===true || false;
@@ -270,8 +262,6 @@ export let Polyline = (function() {
let ymin = Number.POSITIVE_INFINITY
let ymax = Number.NEGATIVE_INFINITY;
let behind = true;
for (var k=0; k<len; k++) {
var xyview = view.aladin.world2pix(this.raDecArray[k][0], this.raDecArray[k][1]);
@@ -380,10 +370,12 @@ export let Polyline = (function() {
let v0 = this.closed ? len - 1 : 0;
let v1 = this.closed ? 0 : 1;
ctx.globalAlpha = this.opacity;
ctx.lineWidth = this.lineWidth;
ctx.beginPath();
for (var k = 0; k < nSegment; k++) {
drawLine(xyView[v0], xyView[v1]);
v0 = v1;
@@ -408,17 +400,15 @@ export let Polyline = (function() {
v1 = v1 + 1;
}
ctx.save();
ctx.fillStyle = this.fillColor;
ctx.globalAlpha = this.opacity;
ctx.fill();
ctx.restore();
}
return true;
};
Polyline.prototype.isInStroke = function(ctx, view, x, y) {
ctx.beginPath()
ctx.lineWidth = this.lineWidth;
let pointXY = [];
@@ -441,8 +431,8 @@ export let Polyline = (function() {
if (v1 && v2) {
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
_drawLine(line, ctx, true);
_drawLine(line, ctx);
if (ctx.isPointInStroke(x, y)) { // x, y is on line?
return true;
}
@@ -455,7 +445,7 @@ export let Polyline = (function() {
if (v1 && v2) {
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
_drawLine(line, ctx, true);
_drawLine(line, ctx);
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;

View File

@@ -80,7 +80,7 @@ export class SAMPConnector {
let params = message["samp.params"];
const {url, name} = params;
const image = aladin.createImageFITS(url, {name}, (e) => window.alert(e));
const image = aladin.createImageFITS(url, {name}, undefined, (e) => window.alert(e));
aladin.setOverlayImageLayer(image, name);
};
@@ -92,6 +92,8 @@ export class SAMPConnector {
let url = params['url'];
let name = params['name'] || id;
console.log(id, url, name)
A.catalogFromURL(
url,
{name, onClick: 'showTable'},
@@ -99,7 +101,7 @@ export class SAMPConnector {
(catalog) => {
aladin.addCatalog(catalog)
},
(e) => window.alert(e)
(e) => window.alert(e),
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

@@ -7,56 +7,61 @@ import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
// For wasm generated by wasm-pack
import wasmPack from 'vite-plugin-wasm-pack';
// To include and minify glsl into the bundle
import glsl from 'vite-plugin-glsl';
// To include css into the bundle
//import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default defineConfig({
build: {
minify: 'esbuild',
lib: {
// Could also be a dictionary or array of multiple entry points
entry: resolve(__dirname, 'src/js/A.js'),
name: 'A',
formats: ["umd", "es"],
// the proper extensions will be added
fileName: 'aladin',
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig(({mode}) => {
return {
build: {
minify: 'esbuild',
lib: {
// Could also be a dictionary or array of multiple entry points
entry: resolve(__dirname, 'src/js/A.js'),
name: 'A',
formats: ["umd", "es"],
// the proper extensions will be added
fileName: 'aladin',
},
rollupOptions: {},
//formats: ["es"],
target: ["es2015", "chrome58", "edge18", "firefox57", "node12", "safari11"],
//target: ["es2015"],
// Relative to the root
outDir: resolve(__dirname, 'dist'),
},
rollupOptions: {},
//formats: ["es"],
target: ["es2015", "chrome58", "edge18", "firefox57", "node12", "safari11"],
//target: ["es2015"],
// Relative to the root
outDir: resolve(__dirname, 'dist'),
},
//publicDir: resolve(__dirname, 'src/img'),
plugins: [
wasm(),
wasmPack(resolve(__dirname, 'src/core')),
topLevelAwait(),
glsl({
compress: true,
}),
],
resolve: {
alias: [
{find: '@', replacement: path.resolve(__dirname, '/src')},
{find: '#', replacement: path.resolve(__dirname, '/tests/unit')},
{find: '$', replacement: path.resolve(__dirname, '/tests/e2e')}
//publicDir: resolve(__dirname, 'src/img'),
plugins: [
wasm(),
wasmPack(resolve(__dirname, 'src/core')),
topLevelAwait(),
mode === 'analyze' &&
visualizer({
filename: 'stats.html', // output file
template: 'treemap', // "sunburst", "treemap", "network"
gzipSize: true, // show gzip sizes
brotliSize: true, // show brotli sizes
open: true // open stats.html automatically
}),
],
},
//test: {
// globals: true,
// environment: 'happy-dom',
// include: [
// 'tests/unit/**/*.{test,spec}.{js,ts}'
// ],
// deps: {
// inline: ['core/pkg'],
// },
//},
server: {
open: '/examples/index.html',
},
resolve: {
alias: [
{find: '@', replacement: path.resolve(__dirname, '/src')},
{find: '#', replacement: path.resolve(__dirname, '/tests/unit')},
{find: '$', replacement: path.resolve(__dirname, '/tests/e2e')}
],
},
//test: {
// globals: true,
// environment: 'happy-dom',
// include: [
// 'tests/unit/**/*.{test,spec}.{js,ts}'
// ],
// deps: {
// inline: ['core/pkg'],
// },
//},
server: {
open: '/examples/index.html',
},
}
});