Compare commits

..

52 Commits

Author SHA1 Message Date
Matthieu Baumann
4781ffdef2 versionning 3.8 2026-01-31 16:44:06 +01:00
Matthieu Baumann
0a48b016f9 fix cargo clippy 2026-01-31 16:41:32 +01:00
Matthieu Baumann
b699a4bc12 toolbar functional 2026-01-31 16:01:51 +01:00
Matthieu Baumann
95dfcc2cff fix some bgs before beta 2026-01-30 01:11:24 +01:00
Matthieu Baumann
c3eca21aee colorpicker valid tooltip 2026-01-29 23:44:45 +01:00
Matthieu Baumann
4524586145 add toolbar 2026-01-29 19:56:42 +01:00
Matthieu Baumann
13c5dc1aa1 toggle refac + toolbar 2026-01-29 19:21:24 +01:00
Matthieu Baumann
b84a444634 fix local hips bug 2026-01-27 18:58:51 +01:00
Matthieu Baumann
e16f34fdad fix displacement bug when the inertia is triggered after a zoom/unzoom 2026-01-22 16:55:03 +01:00
Matthieu Baumann
c7fcf3d451 HiPS browser adjustements 2026-01-22 14:23:21 +01:00
Matthieu Baumann
7960ef9bde wip hips composite ui 2026-01-22 11:30:34 +01:00
Matthieu Baumann
c2e7838930 fix polygonal selection #339 2026-01-22 11:29:50 +01:00
Matthieu Baumann
601928bc8c add settings for catalogs 2026-01-16 14:46:03 +01:00
Matthieu Baumann
feb892fa03 simplify UI Layout 2026-01-15 18:23:33 +01:00
Matthieu Baumann
60022b2659 enhance hips browsing window stability 2026-01-13 16:51:01 +01:00
Matthieu Baumann
c69aa990b6 add floating tooltip 2026-01-10 15:26:05 +01:00
Matthieu Baumann
42209b166e first commit 2026-01-08 17:56:19 +01:00
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
144 changed files with 12794 additions and 3278 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,48 @@
### 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>
* [feat] 'stackChanged' new event informing when a layer has been added, removed or swapped.
* [ui] a new HiPS browser window to search and find HiPS among the HiPS worldwide network.
* [ui] new settings panel for Catalog overlays to change the size, color or shapes of sources
* [ui] possibility to swap 2 layers. Functional but not definitive, it would be better to allow drag and drop amond the layers.
* [fix] fix local HiPS loading.
* [ui] WIP. A toolbar object
* [fix] fix selection of footprints. In the future allow a skew selection mode and a additive selection shortkey.
* [fix] inertia bug when zooming in/out
### 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

17
assets/icons/enlarge.svg Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-9 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>arrow-up-down</title>
<desc>Created with Sketch Beta.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Icon-Set-Filled" sketch:type="MSLayerGroup" transform="translate(-213.000000, -1193.000000)" fill="#000000">
<path d="M225,1217 L222,1217 L222,1201 L225,1201 C225.643,1201 226.293,1201.02 226.687,1200.62 C227.08,1200.23 227.08,1199.6 226.687,1199.2 L220.747,1193.28 C220.537,1193.07 220.259,1192.98 219.984,1193 C219.71,1192.98 219.432,1193.07 219.222,1193.28 L213.283,1199.2 C212.89,1199.6 212.89,1200.23 213.283,1200.62 C213.676,1201.02 214.294,1201 215,1201 L218,1201 L218,1217 L215,1217 C214.357,1217 213.676,1216.98 213.283,1217.38 C212.89,1217.77 212.89,1218.4 213.283,1218.8 L219.222,1224.72 C219.432,1224.93 219.71,1225.02 219.984,1225 C220.259,1225.02 220.537,1224.93 220.747,1224.72 L226.687,1218.8 C227.08,1218.4 227.08,1217.77 226.687,1217.38 C226.293,1216.98 225.737,1217 225,1217" id="arrow-up-down" sketch:type="MSShapeGroup">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

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

6
assets/icons/tree.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tree-view-line</title>
<path d="M15,32H11a1,1,0,0,1-1-1V27a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1v4A1,1,0,0,1,15,32Zm-3-2h2V28H12Z" class="clr-i-outline clr-i-outline-path-1"></path><path d="M15,16H11a1,1,0,0,0-1,1v1.2H5.8V12H7a1,1,0,0,0,1-1V7A1,1,0,0,0,7,6H3A1,1,0,0,0,2,7v4a1,1,0,0,0,1,1H4.2V29.8h6.36a.8.8,0,0,0,0-1.6H5.8V19.8H10V21a1,1,0,0,0,1,1h4a1,1,0,0,0,1-1V17A1,1,0,0,0,15,16ZM4,8H6v2H4ZM14,20H12V18h2Z" class="clr-i-outline clr-i-outline-path-2"></path><path d="M34,9a1,1,0,0,0-1-1H10v2H33A1,1,0,0,0,34,9Z" class="clr-i-outline clr-i-outline-path-3"></path><path d="M33,18H18v2H33a1,1,0,0,0,0-2Z" class="clr-i-outline clr-i-outline-path-4"></path><path d="M33,28H18v2H33a1,1,0,0,0,0-2Z" class="clr-i-outline clr-i-outline-path-5"></path>
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

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

@@ -4,13 +4,16 @@
</head>
<body>
<div id="aladin-lite-div" style="width: 512px; height: 512px"></div>
<div id="aladin-lite-div" style="width: 512px; height: 512px">
<div id="toolbar"></div>
</div>
<script type="module">
import showUrl from './../assets/icons/show.svg';
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {samp: true, survey: "data/hips/PanSTARRS_DR1_color-z-zg-g", fov:2.0, target: "22 35 58.39 +33 57 57.8", showSettingsControl: true, log: false});
aladin = A.aladin('#aladin-lite-div', {toolbar: {divSelector: '#toolbar'}, samp: true, survey: "data/hips/PanSTARRS_DR1_color-z-zg-g", fov:2.0, target: "22 35 58.39 +33 57 57.8", showSettingsControl: true, log: false});
aladin.setProjection('AIT');
let cfht = aladin.createImageSurvey("CFHT", "CFHT MegaCam u+g+r", "./data/hips/CFHT", "equatorial", 10, {imgFormat: 'png'});
let jwst1 = aladin.createImageSurvey("CDS/P/JWST/Stephans-Quintet/NIRCam+MIRI", "JWST NIRCam+MIRI", "data/hips/JWST_NIRCam_MIRI", null, null, {imgFormat: 'png'});
@@ -20,6 +23,16 @@
aladin.setOverlayImageLayer('JWST2', 'overlay_JWST2');
aladin.getOverlayImageLayer('overlay_JWST1').setAlpha(0.0);
aladin.getOverlayImageLayer('overlay_JWST2').setAlpha(0.0);
aladin.toolbar.add('action_custom', {
icon: {
url: showUrl,
size: "medium"
},
action(_) {
console.log("do a thing")
}
});
});
</script>

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

@@ -19,7 +19,6 @@
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
<script src="https://code.jquery.com/jquery-1.10.1.min.js"></script>
<script type="text/javascript">
let aladin;
@@ -38,8 +37,8 @@
<!-- fin temporaire gestion cercle -->
<b>Orientation</b><br>
<button id="hips-coronelli" class="pure-button" name="ref-hips" onclick="longitudeReversed = false; aladin.reverseLongitude(longitudeReversed)">Normal</button><br>
<button id="hips-illenoroc" class="pure-button" name="ref-hips" onclick="longitudeReversed = true; aladin.reverseLongitude(longitudeReversed)">Inversé</button>
<button id="hips-coronelli" class="pure-button" name="ref-hips" onclick="aladin.reverseLongitude(false)">Normal</button><br>
<button id="hips-illenoroc" class="pure-button" name="ref-hips" onclick="aladin.reverseLongitude(true);">Inversé</button>
<br><br>
<b>Constellations</b>
@@ -75,6 +74,13 @@
<a href="#"><img id="coronelli-stars-red" class="catcoro coro-star" src="star_red.png"></a>
<a href="#"><img id="coronelli-stars-blue" class="catcoro coro-star" src="star_blue.png"></a>
</div>
<div>Coronelli2<br>
<!--a id="coronelli-stars" class="pure-button catcoro" href="#">Coronelli</a-->
<a href="#"><img id="coronelli-stars-white" class="catcoro coro-star" src="star_white.png"></a>
<a href="#"><img id="coronelli-stars-yellow" class="catcoro coro-star" src="star_yellow.png"></a><br>
<a href="#"><img id="coronelli-stars-red" class="catcoro coro-star" src="star_red.png"></a>
<a href="#"><img id="coronelli-stars-blue" class="catcoro coro-star" src="star_blue.png"></a>
</div>
<br><br>
<b>Navigation</b>&nbsp;&nbsp;<br><button id="stop">Stop</button>
@@ -99,6 +105,14 @@
<a class="pure-button nav-button nav-goto" href="#">Halley</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
<div id="coo_halley">
<a class="pure-button nav-button nav-goto" href="#">Halley</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
<div id="coo_halley">
<a class="pure-button nav-button nav-goto" href="#">Halley</a><br>
&nbsp;&nbsp;<a class="pure-button nav-button nav-flyto" href="#">Move</a>
</div>
</div>
<style type="text/css"> .aladin-reticleColor { color: rgb(178, 50, 178); font-weight:bold;} </style>
@@ -231,11 +245,10 @@
import {Utils} from '../src/js/Utils';
A.init.then(() => {
var hipsDir="http://alasky.u-strasbg.fr/CDS_P_Coronelli";
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, expandLayersControl: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
aladin.createImageSurvey('Coronelli', 'Coronelli', hipsDir, 'equatorial', 4, {imgFormat: 'jpg'});
aladin.setImageSurvey('Coronelli');
var hipsDir="http://alasky.cds.unistra.fr/CDS_P_Coronelli";
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, lockNorthUp: true, reverseLongitude: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
aladin.createImageSurvey('Coronelli', 'Coronelli', hipsDir, 'equatorial', 4, {imgFormat: 'jpg', minOrder: 3});
aladin.setImageSurvey('Coronelli')
$('#layersControlLeft').show();
$('#layersCL2').show();
$('#layersControlRight').show();
@@ -344,14 +357,14 @@
// listen click on navigation buttons
$('.nav-button').click(function() {
var cooTarget = $(this).parent().attr('id');
var cooTarget = $(this).parent().attr('id');
if ($(this).hasClass("nav-goto")) {
aladin.gotoRaDec(cooNav[cooTarget].ra, cooNav[cooTarget].dec);
}
else if ($(this).hasClass("nav-flyto")) {
aladin.animateToRaDec(cooNav[cooTarget].ra, cooNav[cooTarget].dec, cooNav[cooTarget].time);
}
if ($(this).hasClass("nav-goto")) {
aladin.gotoRaDec(cooNav[cooTarget].ra, cooNav[cooTarget].dec);
}
else if ($(this).hasClass("nav-flyto")) {
aladin.animateToRaDec(cooNav[cooTarget].ra, cooNav[cooTarget].dec, cooNav[cooTarget].time);
}
});
// stop animations

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

@@ -68,8 +68,8 @@
console.log(pos)
});
aladin.on('layerChanged', function(imageLayer, layer, state){
console.log(imageLayer, layer, state)
aladin.on('stackChanged', function(state) {
console.log(state)
});
cat.sources[0].actionClicked();

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

@@ -13,6 +13,9 @@
aladin = A.aladin(
'#aladin-lite-div',
{
toolbar: {
vertical: false,
},
showSimbadPointerControl: true,
survey: 'https://skies.esac.esa.int/AKARI/color/', // set initial image survey
projection: 'AIT', // set a projection
@@ -23,6 +26,8 @@
reticleSize: 64, // change reticle size
showContextMenu: true,
showShareControl: true,
showCooGridControl: true,
showColorPickerControl: true,
showFrame: true,
showZoomControl:true,
showSettingsControl:true,

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.8.0-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.8.0-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

@@ -1,6 +1,6 @@
[package]
name = "al-api"
version = "3.7.0"
version = "3.8.0"
authors = ["baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr"]
edition = "2018"

View File

@@ -179,11 +179,12 @@ impl HiPSProperties {
#[wasm_bindgen]
#[serde(rename_all = "camelCase")]
pub enum ImageExt {
#[serde(alias = "fits", alias = "fits.fz")]
Fits,
Jpeg,
Png,
Webp,
#[serde(alias = "fits.fz")]
FitsFz,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -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

@@ -1,15 +1,13 @@
[package]
name = "al-core"
version = "3.7.0"
version = "3.8.0"
authors = ["baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr"]
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();
@@ -1586,16 +1605,23 @@ impl App {
return;
}
if self.vel_history.len() < 5 {
return;
}
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 +1631,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))
}
@@ -1630,6 +1654,11 @@ impl App {
// For the moment, no animation is triggered.
// The fov is directly set
self.camera.set_aperture(fov, &self.projection);
// reset the parameters that determine if an inertia is needed
self.vel_history.clear();
self.dist_dragging = 0.0;
self.request_for_new_tiles = true;
self.request_redraw = true;
}
@@ -1718,7 +1747,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 +1756,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 +1816,12 @@ impl App {
pub(crate) fn set_zoom_factor(&mut self, zoom_factor: f64) {
self.camera.set_zoom_factor(zoom_factor, &self.projection);
// reset the parameters that determine if an inertia is needed
self.vel_history.clear();
self.dist_dragging = 0.0;
self._update_hips_location();
self.request_for_new_tiles = true;
self.request_redraw = true;
}

View File

@@ -224,6 +224,8 @@ impl CameraViewPort {
self.view_hpx_cells.get_cells(depth, frame)
}
// This method has the role to determine the render mode based on the fov
// For large FoV, raytracing drawing mode, rasterizer otherwise
pub fn is_raytracing(&self, proj: &ProjectionType) -> bool {
// Check whether the tile depth is 0 for square projection
// definition domains i.e. Mercator
@@ -240,6 +242,7 @@ impl CameraViewPort {
ProjectionType::Ait(_) => self.aperture >= 100.0_f64.to_radians(),
ProjectionType::Mol(_) => self.aperture >= 100.0_f64.to_radians(),
ProjectionType::Zea(_) => self.aperture >= 140.0_f64.to_radians(),
_ => self.aperture >= 140.0_f64.to_radians(),
}
}
@@ -315,11 +318,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 +569,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,
)
}
@@ -237,7 +231,7 @@ impl WebClient {
"ZEA" => self
.app
.set_projection(ProjectionType::Zea(mapproj::zenithal::zea::Zea::new())), /* Equal-area */
/*"FEYE" => self
"FEYE" => self
.app
.set_projection(ProjectionType::Feye(mapproj::zenithal::feye::Feye::new())),
"AIR" => {
@@ -245,19 +239,19 @@ impl WebClient {
//air_proj.set_n_iter(10);
//air_proj.set_eps(1e-12);
self.app.set_projection(ProjectionType::Air(air_proj))
}*/
}
//"AZP",
/*"ARC" => self
"ARC" => self
.app
.set_projection(ProjectionType::Arc(mapproj::zenithal::arc::Arc::new())),
"NCP" => self
.app
.set_projection(ProjectionType::Ncp(mapproj::zenithal::ncp::Ncp::new())),*/
.set_projection(ProjectionType::Ncp(mapproj::zenithal::ncp::Ncp::new())),
// Cylindrical
"MER" => self
.app
.set_projection(ProjectionType::Mer(mapproj::cylindrical::mer::Mer::new())),
/*"CAR" => self
"CAR" => self
.app
.set_projection(ProjectionType::Car(mapproj::cylindrical::car::Car::new())),
"CEA" => self
@@ -265,17 +259,17 @@ impl WebClient {
.set_projection(ProjectionType::Cea(mapproj::cylindrical::cea::Cea::new())),
"CYP" => self
.app
.set_projection(ProjectionType::Cyp(mapproj::cylindrical::cyp::Cyp::new())),*/
.set_projection(ProjectionType::Cyp(mapproj::cylindrical::cyp::Cyp::new())),
// Pseudo-cylindrical
"AIT" => self
.app
.set_projection(ProjectionType::Ait(mapproj::pseudocyl::ait::Ait::new())),
/*"PAR" => self
"PAR" => self
.app
.set_projection(ProjectionType::Par(mapproj::pseudocyl::par::Par::new())),
"SFL" => self
.app
.set_projection(ProjectionType::Sfl(mapproj::pseudocyl::sfl::Sfl::new())),*/
.set_projection(ProjectionType::Sfl(mapproj::pseudocyl::sfl::Sfl::new())),
"MOL" => {
let mut mol_proj = mapproj::pseudocyl::mol::Mol::new();
mol_proj.set_n_iter(10);
@@ -283,13 +277,13 @@ impl WebClient {
self.app.set_projection(ProjectionType::Mol(mol_proj))
} // Conic
/*"COD" => self
"COD" => self
.app
.set_projection(ProjectionType::Cod(mapproj::conic::cod::Cod::new())),
// Hybrid
"HPX" => self
.app
.set_projection(ProjectionType::Hpx(mapproj::hybrid::hpx::Hpx::new())),*/
.set_projection(ProjectionType::Hpx(mapproj::hybrid::hpx::Hpx::new())),
_ => Err(JsValue::from_str(
"Not a valid projection name. AIT, ZEA, SIN, STG, TAN, MOL and MER are accepted",
)),
@@ -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

@@ -9,6 +9,7 @@
// World space
use crate::camera::CameraViewPort;
use crate::domain::sdf::ProjDefType;
use crate::math::rotation::Rotation;
use coo_space::XYZModel;
//use crate::num_traits::FloatConst;
@@ -22,7 +23,7 @@ pub mod domain;
use crate::math::angle::ToAngle;
use crate::math::angle::Angle;
use domain::{basic, full::FullScreen};
use domain::{basic, cod::Cod, full::FullScreen, hpx::Hpx, par::Par};
/* S <-> NDC space conversion methods */
pub fn screen_to_ndc_space(
pos_screen_space: &XYScreen<f64>,
@@ -114,15 +115,15 @@ pub enum ProjectionType {
/* ZEA, Equal-area */
Zea(mapproj::zenithal::zea::Zea),
/* FEYE, Fish-eyes */
//Feye(mapproj::zenithal::feye::Feye),
Feye(mapproj::zenithal::feye::Feye),
/* AIR, */
//Air(mapproj::zenithal::air::Air),
Air(mapproj::zenithal::air::Air),
//AZP: {fov: 180},
//Azp(mapproj::zenithal::azp::Azp),
/* ARC, */
//Arc(mapproj::zenithal::arc::Arc),
Arc(mapproj::zenithal::arc::Arc),
/* NCP, */
//Ncp(mapproj::zenithal::ncp::Ncp),
Ncp(mapproj::zenithal::ncp::Ncp),
// Pseudo-cylindrical projections
/* AIT, Aitoff */
@@ -130,26 +131,26 @@ pub enum ProjectionType {
// MOL, Mollweide */
Mol(mapproj::pseudocyl::mol::Mol),
// PAR, */
//Par(mapproj::pseudocyl::par::Par),
Par(mapproj::pseudocyl::par::Par),
// SFL, */
//Sfl(mapproj::pseudocyl::sfl::Sfl),
Sfl(mapproj::pseudocyl::sfl::Sfl),
// Cylindrical projections
// MER, Mercator */
Mer(mapproj::cylindrical::mer::Mer),
// CAR, */
//Car(mapproj::cylindrical::car::Car),
Car(mapproj::cylindrical::car::Car),
// CEA, */
//Cea(mapproj::cylindrical::cea::Cea),
Cea(mapproj::cylindrical::cea::Cea),
// CYP, */
//Cyp(mapproj::cylindrical::cyp::Cyp),
Cyp(mapproj::cylindrical::cyp::Cyp),
// Conic projections
// COD, */
//Cod(mapproj::conic::cod::Cod),
Cod(mapproj::conic::cod::Cod),
// HEALPix hybrid projection
//Hpx(mapproj::hybrid::hpx::Hpx),
Hpx(mapproj::hybrid::hpx::Hpx),
}
use crate::math::lonlat::LonLat;
@@ -305,38 +306,38 @@ impl ProjectionType {
/* ZEA, Equal-area */
ProjectionType::Zea(_) => 1.0,
/* FEYE, Fish-eyes */
//ProjectionType::Feye(_) => 1.0,
ProjectionType::Feye(_) => 1.0,
/* AIR, */
//ProjectionType::Air(_) => 1.0,
ProjectionType::Air(_) => 1.0,
//AZP: {fov: 180},
//Azp(mapproj::zenithal::azp::Azp),
/* ARC, */
//ProjectionType::Arc(_) => 1.0,
ProjectionType::Arc(_) => 1.0,
/* NCP, */
//ProjectionType::Ncp(_) => 1.0,
ProjectionType::Ncp(_) => 1.0,
// Pseudo-cylindrical projections
/* AIT, Aitoff */
ProjectionType::Ait(_) => 2.0,
// MOL, Mollweide */
ProjectionType::Mol(_) => 2.0,
// PAR, */
//ProjectionType::Par(_) => 2.0,
ProjectionType::Par(_) => 2.0,
// SFL, */
//ProjectionType::Sfl(_) => 2.0,
ProjectionType::Sfl(_) => 2.0,
// Cylindrical projections
// MER, Mercator */
ProjectionType::Mer(_) => 1.0,
// CAR, */
//ProjectionType::Car(_) => 1.0,
ProjectionType::Car(_) => 1.0,
// CEA, */
//ProjectionType::Cea(_) => 1.0,
ProjectionType::Cea(_) => 1.0,
// CYP, */
//ProjectionType::Cyp(_) => 1.0,
ProjectionType::Cyp(_) => 1.0,
// Conic projections
// COD, */
//ProjectionType::Cod(_) => 1.0,
ProjectionType::Cod(_) => 1.0,
// HEALPix hybrid projection
//ProjectionType::Hpx(_) => 2.0,
ProjectionType::Hpx(_) => 2.0,
}
}
@@ -352,15 +353,15 @@ impl ProjectionType {
/* ZEA, Equal-area */
ProjectionType::Zea(_) => 359.9_f64.to_radians().to_angle(),
/* FEYE, Fish-eyes */
//ProjectionType::Feye(_) => 190.0,
ProjectionType::Feye(_) => 190.0_f64.to_radians().to_angle(),
/* AIR, */
//ProjectionType::Air(_) => 360.0,
ProjectionType::Air(_) => 360.0_f64.to_radians().to_angle(),
//AZP: {fov: 180},
//Azp(mapproj::zenithal::azp::Azp),
//ProjectionType::Azp(mapproj::zenithal::azp::Azp),
/* ARC, */
//ProjectionType::Arc(_) => 360.0,
ProjectionType::Arc(_) => 360.0_f64.to_radians().to_angle(),
/* NCP, */
//ProjectionType::Ncp(_) => 180.0,
ProjectionType::Ncp(_) => 180.0_f64.to_radians().to_angle(),
// Pseudo-cylindrical projections
/* AIT, Aitoff */
@@ -368,26 +369,26 @@ impl ProjectionType {
// MOL, Mollweide */
ProjectionType::Mol(_) => 360.0_f64.to_radians().to_angle(),
// PAR, */
//ProjectionType::Par(_) => 360.0,
ProjectionType::Par(_) => 360.0_f64.to_radians().to_angle(),
// SFL, */
//ProjectionType::Sfl(_) => 360.0,
ProjectionType::Sfl(_) => 360.0_f64.to_radians().to_angle(),
// Cylindrical projections
// MER, Mercator */
ProjectionType::Mer(_) => 360.0_f64.to_radians().to_angle(),
// CAR, */
//ProjectionType::Car(_) => 360.0,
ProjectionType::Car(_) => 360.0_f64.to_radians().to_angle(),
// CEA, */
//ProjectionType::Cea(_) => 360.0,
ProjectionType::Cea(_) => 360.0_f64.to_radians().to_angle(),
// CYP, */
//ProjectionType::Cyp(_) => 360.0,
ProjectionType::Cyp(_) => 360.0_f64.to_radians().to_angle(),
// Conic projections
// COD, */
//ProjectionType::Cod(_) => 330.0,
ProjectionType::Cod(_) => 330.0_f64.to_radians().to_angle(),
// HEALPix hybrid projection
//ProjectionType::Hpx(_) => 360.0,
ProjectionType::Hpx(_) => 360.0_f64.to_radians().to_angle(),
}
}
@@ -415,27 +416,27 @@ impl ProjectionType {
&DISK
}
/* FEYE, Fish-eyes */
/*ProjectionType::Feye(_) => {
ProjectionType::Feye(_) => {
const DISK: ProjDefType = ProjDefType::Disk(basic::disk::Disk { radius: 1.0 });
&DISK
}*/
}
/* AIR, */
/*ProjectionType::Air(_) => {
ProjectionType::Air(_) => {
const DISK: ProjDefType = ProjDefType::FullScreen(FullScreen);
&DISK
}*/
}
//AZP: {fov: 180},
//Azp(mapproj::zenithal::azp::Azp),
/* ARC, */
/*ProjectionType::Arc(_) => {
ProjectionType::Arc(_) => {
const DISK: ProjDefType = ProjDefType::Disk(basic::disk::Disk { radius: 1.0 });
&DISK
}*/
}
/* NCP, */
/*ProjectionType::Ncp(_) => {
ProjectionType::Ncp(_) => {
const DISK: ProjDefType = ProjDefType::Disk(basic::disk::Disk { radius: 1.0 });
&DISK
}*/
}
// Pseudo-cylindrical projections
/* AIT, Aitoff */
@@ -449,15 +450,15 @@ impl ProjectionType {
&ELLIPSE
}
// PAR, */
/*ProjectionType::Par(_) => {
ProjectionType::Par(_) => {
const PAR: ProjDefType = ProjDefType::Par(Par);
&PAR
}*/
}
// SFL, */
/*ProjectionType::Sfl(_) => {
ProjectionType::Sfl(_) => {
const PAR: ProjDefType = ProjDefType::Par(Par);
&PAR
}*/
}
// Cylindrical projections
// MER, Mercator */
@@ -465,32 +466,32 @@ impl ProjectionType {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
} // CAR, */
/*ProjectionType::Car(_) => {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
}*/
// CEA, */
/*ProjectionType::Cea(_) => {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
}*/
// CYP, */
/*ProjectionType::Cyp(_) => {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
}*/
ProjectionType::Car(_) => {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
}
// CEA, */
ProjectionType::Cea(_) => {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
}
// CYP, */
ProjectionType::Cyp(_) => {
const FULL_SCREEN: ProjDefType = ProjDefType::FullScreen(FullScreen);
&FULL_SCREEN
}
// Conic projections
// COD, */
/*ProjectionType::Cod(_) => {
const CONIC: ProjDefType = ProjDefType::Cod(Cod::new());
&CONIC
}*/
// HEALPix hybrid projection
/*ProjectionType::Hpx(_) => {
const HPX_DEF_REG: ProjDefType = ProjDefType::Hpx(Hpx);
&HPX_DEF_REG
}*/
// Conic projections
// COD, */
ProjectionType::Cod(_) => {
const CONIC: ProjDefType = ProjDefType::Cod(Cod::new());
&CONIC
}
// HEALPix hybrid projection
ProjectionType::Hpx(_) => {
const HPX_DEF_REG: ProjDefType = ProjDefType::Hpx(Hpx);
&HPX_DEF_REG
}
}
}
}
@@ -509,15 +510,15 @@ impl Projection for ProjectionType {
/* ZEA, Equal-area */
ProjectionType::Zea(zea) => zea.clip_to_world_space(xy),
/* FEYE, Fish-eyes */
//ProjectionType::Feye(feye) => feye.clip_to_world_space(xy),
ProjectionType::Feye(feye) => feye.clip_to_world_space(xy),
/* AIR, */
//ProjectionType::Air(air) => air.clip_to_world_space(xy),
ProjectionType::Air(air) => air.clip_to_world_space(xy),
//AZP: {fov: 180},
//Azp(mapproj::zenithal::azp::Azp),
/* ARC, */
//ProjectionType::Arc(arc) => arc.clip_to_world_space(xy),
ProjectionType::Arc(arc) => arc.clip_to_world_space(xy),
/* NCP, */
//ProjectionType::Ncp(ncp) => ncp.clip_to_world_space(xy),
ProjectionType::Ncp(ncp) => ncp.clip_to_world_space(xy),
// Pseudo-cylindrical projections
/* AIT, Aitoff */
@@ -525,30 +526,34 @@ impl Projection for ProjectionType {
// MOL, Mollweide */
ProjectionType::Mol(mol) => mol.clip_to_world_space(xy),
// PAR, */
//ProjectionType::Par(par) => par.clip_to_world_space(xy),
ProjectionType::Par(par) => par.clip_to_world_space(xy),
// SFL, */
//ProjectionType::Sfl(sfl) => sfl.clip_to_world_space(xy),
ProjectionType::Sfl(sfl) => sfl.clip_to_world_space(xy),
// Cylindrical projections
// MER, Mercator */
ProjectionType::Mer(mer) => mer.clip_to_world_space(xy),
// CAR, */
//ProjectionType::Car(car) => car.clip_to_world_space(xy),
ProjectionType::Car(car) => car.clip_to_world_space(xy),
// CEA, */
//ProjectionType::Cea(cea) => cea.clip_to_world_space(xy),
ProjectionType::Cea(cea) => cea.clip_to_world_space(xy),
// CYP, */
//ProjectionType::Cyp(cyp) => cyp.clip_to_world_space(xy),
ProjectionType::Cyp(cyp) => cyp.clip_to_world_space(xy),
// Conic projections
// COD, */
/*ProjectionType::Cod(cod) => cod.clip_to_world_space(xy).map(|xyz| {
ProjectionType::Cod(cod) => cod.clip_to_world_space(xy).map(|xyz| {
let rot = Rotation::from_sky_position(
&LonLatT::new(0.0_f64.to_angle(), (HALF_PI * 0.5).to_angle()).vector(),
&LonLatT::new(
0.0_f64.to_angle(),
(mapproj::math::HALF_PI * 0.5).to_angle(),
)
.vector(),
);
rot.inv_rotate(&xyz)
}),*/
}),
// HEALPix hybrid projection
//ProjectionType::Hpx(hpx) => hpx.clip_to_world_space(xy),
ProjectionType::Hpx(hpx) => hpx.clip_to_world_space(xy),
}
}
@@ -565,15 +570,15 @@ impl Projection for ProjectionType {
/* ZEA, Equal-area */
ProjectionType::Zea(zea) => zea.world_to_clip_space(xyz),
/* FEYE, Fish-eyes */
//ProjectionType::Feye(feye) => feye.world_to_clip_space(xyz),
ProjectionType::Feye(feye) => feye.world_to_clip_space(xyz),
/* AIR, */
//ProjectionType::Air(air) => air.world_to_clip_space(xyz),
ProjectionType::Air(air) => air.world_to_clip_space(xyz),
//AZP: {fov: 180},
//Azp(mapproj::zenithal::azp::Azp),
/* ARC, */
//ProjectionType::Arc(arc) => arc.world_to_clip_space(xyz),
ProjectionType::Arc(arc) => arc.world_to_clip_space(xyz),
/* NCP, */
//ProjectionType::Ncp(ncp) => ncp.world_to_clip_space(xyz),
ProjectionType::Ncp(ncp) => ncp.world_to_clip_space(xyz),
// Pseudo-cylindrical projections
/* AIT, Aitoff */
@@ -581,30 +586,34 @@ impl Projection for ProjectionType {
// MOL, Mollweide */
ProjectionType::Mol(mol) => mol.world_to_clip_space(xyz),
// PAR, */
//ProjectionType::Par(par) => par.world_to_clip_space(xyz),
ProjectionType::Par(par) => par.world_to_clip_space(xyz),
// SFL, */
//ProjectionType::Sfl(sfl) => sfl.world_to_clip_space(xyz),
ProjectionType::Sfl(sfl) => sfl.world_to_clip_space(xyz),
// Cylindrical projections
// MER, Mercator */
ProjectionType::Mer(mer) => mer.world_to_clip_space(xyz),
// CAR, */
//ProjectionType::Car(car) => car.world_to_clip_space(xyz),
ProjectionType::Car(car) => car.world_to_clip_space(xyz),
// CEA, */
//ProjectionType::Cea(cea) => cea.world_to_clip_space(xyz),
ProjectionType::Cea(cea) => cea.world_to_clip_space(xyz),
// CYP, */
//ProjectionType::Cyp(cyp) => cyp.world_to_clip_space(xyz),
ProjectionType::Cyp(cyp) => cyp.world_to_clip_space(xyz),
// Conic projections
// COD, */
/*ProjectionType::Cod(cod) => {
ProjectionType::Cod(cod) => {
// The Cod projection is centered on (0, 45 deg)
let rot = Rotation::from_sky_position(
&LonLatT::new(0.0_f64.to_angle(), (HALF_PI * 0.5).to_angle()).vector(),
&LonLatT::new(
0.0_f64.to_angle(),
(mapproj::math::HALF_PI * 0.5).to_angle(),
)
.vector(),
);
cod.world_to_clip_space(&rot.rotate(&xyz))
}*/
cod.world_to_clip_space(&rot.rotate(xyz))
}
// HEALPix hybrid projection
//ProjectionType::Hpx(hpx) => hpx.world_to_clip_space(xyz),
ProjectionType::Hpx(hpx) => hpx.world_to_clip_space(xyz),
}
}
}
@@ -634,6 +643,7 @@ impl UniformType for ProjectionType {
// Cylindrical projections
// MER, Mercator */
ProjectionType::Mer(_) => gl.uniform1i(location, 6),
_ => gl.uniform1i(location, 6),
}
}
}
@@ -808,7 +818,7 @@ mod tests {
"./../img/zea.jpg",
ProjectionType::Zea(mapproj::zenithal::zea::Zea),
);
/*generate_projection_map(
generate_projection_map(
"./../img/feye.png",
ProjectionType::Feye(mapproj::zenithal::feye::Feye),
);
@@ -823,14 +833,14 @@ mod tests {
generate_projection_map(
"./../img/air.png",
ProjectionType::Air(mapproj::zenithal::air::Air::new()),
);*/
);
// Cylindrical
generate_projection_map(
"./../img/mer.jpg",
ProjectionType::Mer(mapproj::cylindrical::mer::Mer),
);
/*generate_projection_map(
generate_projection_map(
"./../img/car.png",
ProjectionType::Car(mapproj::cylindrical::car::Car),
);
@@ -841,26 +851,26 @@ mod tests {
generate_projection_map(
"./../img/cyp.png",
ProjectionType::Cyp(mapproj::cylindrical::cyp::Cyp::new()),
);*/
);
// Pseudo-cylindrical
generate_projection_map(
"./../img/ait.jpg",
ProjectionType::Ait(mapproj::pseudocyl::ait::Ait),
);
/*generate_projection_map(
generate_projection_map(
"./../img/car.png",
ProjectionType::Par(mapproj::pseudocyl::par::Par),
);
generate_projection_map(
"./../img/cea.png",
ProjectionType::Sfl(mapproj::pseudocyl::sfl::Sfl),
);*/
);
generate_projection_map(
"./../img/mol.jpg",
ProjectionType::Mol(mapproj::pseudocyl::mol::Mol::new()),
);
// Conic
/*generate_projection_map(
generate_projection_map(
"./../img/cod.png",
ProjectionType::Cod(mapproj::conic::cod::Cod::new()),
);
@@ -868,6 +878,6 @@ mod tests {
generate_projection_map(
"./../img/hpx.png",
ProjectionType::Hpx(mapproj::hybrid::hpx::Hpx),
);*/
);
}
}

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>()));

File diff suppressed because it is too large Load Diff

View File

@@ -97,13 +97,37 @@ 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()
}
// Associate the CSS inside the div
var cssStyleSheet = document.createElement('style')
cssStyleSheet.classList.add("aladin-css");
cssStyleSheet.innerHTML = aladinCSS;
divElement.appendChild(cssStyleSheet)
return new Aladin(divElement, options);
let aladin = new Aladin(divElement, options);
aladin._applyTheme(theme)
return aladin;
};
/**
@@ -362,7 +386,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) {
@@ -881,7 +905,7 @@ A.catalogFromSkyBot = function (ra, dec, radius, epoch, queryOptions, options, s
* @param {function} [options.action] - The callback function to execute when the button is clicked.
* @param {string} [options.title] - The title attribute for the button.
* @param {Object} [options.icon] - An icon object for the button.
* @param {boolean} [options.disable=false] - Whether the button is initially disabled.
* @param {boolean} [options.disabled=false] - Whether the button is initially disabled.
* @param {HTMLElement|string|Widget} [options.content] - The content to be added to the button.
* @param {CSSStyleSheet} [options.cssStyle] - The CSS styles to apply to the button.
* @param {Object} [options.tooltip] - A tooltip.

View File

@@ -42,7 +42,7 @@ import { Coo } from "./libs/astro/coo.js";
import { CooConversion } from "./CooConversion.js";
import { HiPSCache } from "./HiPSCache.js";
import { HiPSList } from "./DefaultHiPSList.js";
import { Toolbar } from "./gui/Toolbar.js";
import { ProjectionEnum } from "./ProjectionEnum.js";
import { ALEvent } from "./events/ALEvent.js";
@@ -64,12 +64,11 @@ import A from "./A.js";
import { StatusBarBox } from "./gui/Box/StatusBarBox.js";
import { FullScreenActionButton } from "./gui/Button/FullScreen.js";
import { ProjectionActionButton } from "./gui/Button/Projection.js";
import { Stack } from "./gui/Button/Stack.js";
// features
import { SettingsButton } from "./gui/Button/Settings";
import { SimbadPointer } from "./gui/Button/SimbadPointer";
import { ColorPicker } from "./gui/Button/ColorPicker";
import { OverlayStackButton } from "./gui/Button/OverlayStack";
import { GridEnabler } from "./gui/Button/GridEnabler";
import { CooFrame } from "./gui/Input/CooFrame";
import { Circle } from "./shapes/Circle";
@@ -152,9 +151,14 @@ 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.
* @property {Object} [toolbar] - Toolbar object
* @property {string} [toolbar.divSelector="null"] - A selector to put the toolbar in. By default the toolbar will be inserted in the Aladin Lite view.
* @property {string} [toolbar.position="topleft"] - Can be 'topleft', 'topright', 'bottomleft', 'bottomright'. Default to 'topleft'
* @property {boolean} [toolbar.vertical=true] - Is the toolbar horizontal or not. Default to vertical
*
* @example
* let aladin = A.aladin({
@@ -253,7 +257,7 @@ import { Polyline } from "./shapes/Polyline";
*/
/**
* @typedef {('select'|'objectsSelected'|'objectClicked'|'objectHovered'|'objectHoveredStop'|'footprintClicked'|'footprintHovered'|'positionChanged'|'zoomChanged'|'rotationChanged'|'click'|'rightClickMove'|'mouseMove'|'wheelTriggered'|'fullScreenToggled'|'cooFrameChanged'|'resizeChanged'|'projectionChanged'|'layerChanged')} EventListener
* @typedef {('select'|'objectsSelected'|'objectClicked'|'objectHovered'|'objectHoveredStop'|'footprintClicked'|'footprintHovered'|'positionChanged'|'zoomChanged'|'rotationChanged'|'click'|'rightClickMove'|'mouseMove'|'wheelTriggered'|'fullScreenToggled'|'cooFrameChanged'|'resizeChanged'|'projectionChanged'|'stackChanged')} EventListener
*
* <ul>
* <li>'positionChanged' is triggered when the view position has been changed. It gives the user the new center position of the view in ICRS frame. See {@link positionChangedParam}</li>
@@ -261,6 +265,7 @@ import { Polyline } from "./shapes/Polyline";
* <li>'mouseMove' is triggered when the mouse move over the view. It gives the the user the new position of the cursor in the current frame. See {@link mouseMoveParam}</li>
* <li>'wheelTriggered' allows to redefine the zooming. Listening for it will disable the default zooming heuristic.</li>
* <li>'objectsSelected', 'objectClicked', 'objectHovered', 'objectHoveredStop', 'footprintClicked', 'footprintHovered' are triggered when a catalog source/footprint has been clicked, hovered, ...
* <li>'stackChanged' is triggered when a layer has been added, removed or swapped. The callback passed is an object having fields. The layer object that has been added/removed (or the swapped layers) and a flag that tells you if it has been 'added', 'removed' or 'swapped'.
* </ul>
*/
@@ -300,14 +305,32 @@ export let Aladin = (function () {
const self = this;
ALEvent.HIPS_LAYER_ADDED.listenedBy(aladinDiv, (imageLayer) => {
this.callbacksByEventName["layerChanged"] &&
this.callbacksByEventName["layerChanged"](imageLayer.detail.layer, imageLayer.detail.layer.layer, "ADDED");
ALEvent.LAYER_ADDED.listenedBy(aladinDiv, (e) => {
const {layer} = e.detail;
let callback = this.callbacksByEventName["stackChanged"];
callback && callback({
change: 'added',
layer,
});
});
ALEvent.HIPS_LAYER_REMOVED.listenedBy(aladinDiv, (imageLayer) => {
this.callbacksByEventName["layerChanged"] &&
this.callbacksByEventName["layerChanged"](imageLayer.detail.layer, imageLayer.detail.layer.layer, "REMOVED");
ALEvent.LAYER_REMOVED.listenedBy(aladinDiv, (e) => {
const {layer} = e.detail;
let callback = this.callbacksByEventName["stackChanged"];
callback && callback({
change: 'removed',
layer
});
});
ALEvent.LAYER_SWAPPED.listenedBy(aladinDiv, (e) => {
const {layer1, layer2} = e.detail;
let callback = this.callbacksByEventName["stackChanged"];
callback && callback({
change: 'swapped',
layer1,
layer2
});
});
// if not options was set, try to retrieve them from the query string
@@ -334,6 +357,10 @@ export let Aladin = (function () {
...Aladin.DEFAULT_OPTIONS.gridOptions,
...requestedOptions.gridOptions
},
toolbar: {
...Aladin.DEFAULT_OPTIONS.toolbar,
...requestedOptions.toolbar
},
// and use the slice method to create a new array for the hipsList property:
// https://stackoverflow.com/questions/7486085/copy-array-by-value
hipsList: requestedOptions.hipsList || Aladin.DEFAULT_OPTIONS.hipsList.slice()
@@ -357,6 +384,11 @@ export let Aladin = (function () {
this.reticle = new Reticle(this.options, this);
this.popup = new Popup(this.aladinDiv, this.view);
this.tooltip = document.createElement('div')
this.tooltip.id = 'aladin-tooltip-mouse';
this.tooltip.classList.add("aladin-box")
this.aladinDiv.appendChild(this.tooltip)
this.ui = [];
@@ -394,7 +426,7 @@ export let Aladin = (function () {
}
}
// Format the hipslist given by the user before storing it in the aladin objec
// Format the hipslist given by the user before storing it in the aladin instance
this.hipsFavorites = [];
let hipsList = [].concat(options.hipsList);
@@ -420,7 +452,7 @@ export let Aladin = (function () {
name = hips.name || hips.id || hips.url;
hipsObj = { ...hipsObj, ...hips };
hipsObj = { ...hips };
} else {
console.warn(
"unable to parse the survey list item: ",
@@ -439,9 +471,6 @@ export let Aladin = (function () {
hipsObj["name"] = name;
}
// at least id or url is defined
//let key = name || id || url;
// Merge what is already in the cache for that HiPS with new properties
// coming from the MOCServer
this.hipsFavorites.push(hipsObj);
@@ -531,9 +560,9 @@ export let Aladin = (function () {
// set right click context menu
if (options.showContextMenu) {
this.contextMenu = new ContextMenu(this);
this.contextMenu.attach(
DefaultActionsForContextMenu.getDefaultActions(this)
DefaultActionsForContextMenu.getDefaultActions(this),
null
);
}
@@ -556,9 +585,25 @@ export let Aladin = (function () {
}
};
Aladin.prototype._applyTheme = function(newTheme) {
this.aladinDiv.setAttribute("data-theme", newTheme);
this.toolbar.el.setAttribute("data-theme", newTheme);
}
Aladin.prototype._setupUI = function (options) {
let self = this;
this.contextMenu = new ContextMenu(this);
let toolbarDivSelector = options && options.toolbar.divSelector || this.aladinDiv;
if (!(toolbarDivSelector instanceof HTMLElement)) {
toolbarDivSelector = document.querySelector(toolbarDivSelector);
}
this.toolbar = new Toolbar({
classList: ["aladin-widgets-toolbar"],
...options.toolbar
}, toolbarDivSelector)
// Status bar
if (options.showStatusBar) {
this.statusBar = new StatusBarBox(this);
@@ -581,48 +626,48 @@ export let Aladin = (function () {
}
////////////////////////////////////////////////////
let stack = new OverlayStackButton(this);
let simbad = new SimbadPointer(this);
let colorPicker = new ColorPicker(this);
let grid = new GridEnabler(this);
this.addUI(stack);
this.addUI(simbad);
this.addUI(grid);
this.addUI(colorPicker)
let widgets = {};
// Add the layers control
if (!options.showLayersControl) {
stack._hide();
}
// Add the simbad pointer control
if (!options.showSimbadPointerControl) {
simbad._hide();
}
// Add the projection control
// Add the coo grid control
if (!options.showCooGridControl) {
grid._hide();
}
// Add the projection control
// Add the coo grid control
if (!options.showColorPickerControl) {
colorPicker._hide();
if (options.showLayersControl) {
let stack = new Stack(this);
widgets["stack"] = stack
}
// Settings control
if (options.showSettingsControl) {
let settings = new SettingsButton(this, {
features: { stack, simbad, grid },
});
this.addUI(settings);
let settings = new SettingsButton(this);
widgets["settings"] = settings
}
// Add the simbad pointer control
if (options.showSimbadPointerControl) {
let simbad = new SimbadPointer(this);
widgets["simbad"] = simbad
}
// Add the projection control
// Add the coo grid control
if (options.showCooGridControl) {
let grid = new GridEnabler(this);
widgets["grid"] = grid;
}
// Add the projection control
// Add the coo grid control
if (options.showColorPickerControl) {
let picker = new ColorPicker(this);
widgets["picker"] = picker;
}
// share control panel
if (options.showShareControl) {
this.addUI(new ShareActionButton(self));
let share = new ShareActionButton(this);
widgets["share"] = share;
}
for (let [name, widget] of Object.entries(widgets)) {
this.toolbar.add(name, widget);
}
if (options.showProjectionControl) {
@@ -749,7 +794,12 @@ export let Aladin = (function () {
longitudeReversed: false,
realFullscreen: false,
pixelateCanvas: true,
manualSelection: false
manualSelection: false,
toolbar: {
divSelector: null,
vertical: true,
position: 'topleft'
}
};
/**
@@ -763,7 +813,7 @@ export let Aladin = (function () {
self.isInFullscreen = !self.isInFullscreen;
ContextMenu.hideAll();
this.contextMenu && this.contextMenu._hide();
this.ui.forEach(ui => {
if (ui.toggle) {
@@ -941,7 +991,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";
@@ -1096,7 +1146,6 @@ export let Aladin = (function () {
return projName;
};
``;
/**
* Returns the current coordinate system: possible values are 'ICRS', 'ICRSd', and 'Galactic' .
@@ -1113,6 +1162,16 @@ export let Aladin = (function () {
return this.view.cooFrame.label;
};
/**
* Get a reference to the Aladin Lite toolbar object. User can append, remove DOMElement/widgets to it
*
* @memberof Aladin
* @returns {Toolbar}
*/
Aladin.prototype.getToolbar = function () {
return this.toolbar;
};
/**
* Moves the Aladin instance to the specified astronomical object.
*
@@ -1771,14 +1830,6 @@ export let Aladin = (function () {
name: hips.name,
})
// and resort the favorites
this.hipsFavorites.sort((h1, h2) => {
let k1 = h1.name || h1.id || h1.url;
let k2 = h2.name || h2.id || h2.url;
return k1 < k2;
})
// send the final event
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document.body, this.hipsFavorites);
}
@@ -2004,7 +2055,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 +2065,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]);
};
/**
@@ -2230,7 +2281,7 @@ export let Aladin = (function () {
"cooFrameChanged",
"resizeChanged",
"projectionChanged",
"layerChanged"
"stackChanged"
];
/**
@@ -2288,9 +2339,9 @@ export let Aladin = (function () {
console.log("positionChanged", ra, dec)
})
aladin.on("layerChanged", (layer, layerName, state) => {
console.log("layerChanged", layer, layerName, state)
})
aladin.on('stackChanged', function(state) {
console.log(state)
});
*/
Aladin.prototype.on = function (what, myFunction) {
if (Aladin.AVAILABLE_CALLBACKS.indexOf(what) < 0) {
@@ -3003,7 +3054,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 +3078,7 @@ aladin.displayFITS(
options,
successCallback,
errorCallback,
layer = "base"
layer
) {
successCallback =
successCallback ||
@@ -3041,6 +3092,7 @@ aladin.displayFITS(
successCallback,
errorCallback
);
layer = layer || (this.view.overlayLayers && this.view.overlayLayers[0])
return this.setOverlayImageLayer(image, layer);
};
@@ -3117,7 +3169,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

@@ -112,11 +112,6 @@ export let Footprint= (function() {
this.overlay.reportChange();
return;
}
/*let catalog = this.getCatalog();
if (catalog) {
catalog.view && catalog.view.requestRedraw();
}*/
};
Footprint.prototype.unhover = function() {
@@ -209,35 +204,6 @@ export let Footprint= (function() {
};
Footprint.prototype.intersectsBBox = function(x, y, w, h, view) {
/*if(this.source) {
let s = this.source;
if (!s.isShowing) {
return false;
}
let c = null;
if (s.x && s.y) {
c = {
x: s.x,
y: s.y,
};
} else {
var xy = view.aladin.world2pix(s.ra, s.dec);
if (!xy) {
return false;
}
c = {
x: xy[0],
y: xy[1],
};
}
if (c.x >= x && c.x <= x + w && c.y >= y && c.y <= y + h) {
return true;
}
}*/
return this.shapes.some((shape) => shape.intersectsBBox(x, y, w, h, view));
};

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;
@@ -336,7 +339,6 @@ export let HiPS = (function () {
await HiPSProperties.fetchFromFile(self.localFiles["properties"])
.then((p) => {
self._parseProperties(p);
self.url = "local";
delete self.localFiles["properties"]
@@ -729,9 +731,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})
};
/**
@@ -860,7 +869,7 @@ export let HiPS = (function () {
imgFormat: this.imgFormat,
});
// once the meta have been well parsed, we can set the meta
ALEvent.HIPS_LAYER_CHANGED.dispatchedTo(this.view.aladinDiv, {
ALEvent.LAYER_CHANGED.dispatchedTo(this.view.aladinDiv, {
layer: this,
});
}
@@ -908,7 +917,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 +931,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)
}
@@ -1065,7 +1074,19 @@ export let HiPS = (function () {
}
};
HiPS.prototype._add2View = function (layer) {
HiPS.prototype._removeFromView = function() {
if (!this.view)
return;
if (this.added) {
this.view.wasm.removeLayer(this.layer);
}
};
HiPS.prototype._addToView = function (layer) {
if (!this.view)
return this;
this.layer = layer;
let self = this;

86
src/js/HiPSComposite.js Normal file
View File

@@ -0,0 +1,86 @@
// Copyright 2013 - 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.
//
/******************************************************************************
* Aladin Lite project
*
* File HiPSComposite.js
*
* Authors: Matthieu Baumann [CDS]
*
*****************************************************************************/
import { HiPS } from "./HiPS.js";
import A from "./A.js";
export let HiPSComposite = (function () {
/**
* The object describing the color composition of image surveys
*
* @class
* @constructs HiPSComposite
*
* @param {HiPSOptions[]} [options] - The option for the survey
*/
function HiPSComposite(options) {
this.name = options && options.name || 'Composite HiPS';
this.hipses = []
};
HiPSComposite.prototype.setOptions = function(options) {
this.hipses = [];
for (var hipsOptions of options) {
let hips = A.HiPS(hipsOptions.id, hipsOptions)
this.hipses.push(hips)
}
};
HiPSComposite.prototype._setView = HiPS.prototype._setView;
HiPSComposite.prototype._addToView = function (layer) {
this._removeFromView();
this.layer = layer;
let i = 0;
for (var hips of this.hipses) {
hips._addToView(layer + '_' + i)
i++;
}
return this
};
HiPSComposite.prototype._removeFromView = function() {
for (var hips of this.hipses) {
hips._removeFromView()
}
}
HiPSComposite.prototype.getOpacity = function() {
return this.hipses[0].getOpacity()
}
HiPSComposite.prototype.setOpacity = function(opacity) {
for (var hips of this.hipses) {
hips.setOpacity(opacity)
}
}
return HiPSComposite;
})();

View File

@@ -362,7 +362,9 @@ export let Image = (function () {
// Private method for updating the view with the new meta
Image.prototype._updateMetadata = HiPS.prototype._updateMetadata;
Image.prototype._add2View = function (layer) {
Image.prototype._removeFromView = HiPS.prototype._removeFromView;
Image.prototype._addToView = function (layer) {
this.layer = layer;
let self = this;
@@ -402,6 +404,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

@@ -30,7 +30,6 @@
*
*****************************************************************************/
import { Color } from "./Color.js"
import { Icon } from "./gui/Widgets/Icon.js";
import { Tabs } from "./gui/Widgets/Tab.js";
import { Table } from "./gui/Widgets/Table.js";
@@ -77,7 +76,7 @@ export let MeasurementTable = (function() {
tooltip: {
global: true,
aladin: this.aladin,
content: 'Press Shift + mouse wheel for scrolling horizontally'
content: 'Scroll to see more...'
},
aladin: this.aladin,
layout,

View File

@@ -51,8 +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_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",
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,dataproduct_subtype,hips_service_url,hips_initial_ra,hips_initial_dec,hips_initial_fov,em_min,em_max",
};
this._allHiPSes = Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
@@ -97,8 +96,7 @@ export class MocServer {
expr: "dataproduct_type=image&&ID=" + ids.join(','),
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
//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",
fields: "ID,hips_creator,hips_copyright,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,dataproduct_subtype,hips_service_url,hips_initial_ra,hips_initial_dec,hips_initial_fov,em_min,em_max",
};
return Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
@@ -114,7 +112,6 @@ export class MocServer {
get: "record",
fmt: "json",
fields: "ID,hips_copyright,obs_title,obs_description,obs_copyright,cs_service_url,hips_service_url",
//fields: "ID,hips_copyright,hips_order,hips_order_min,obs_title,obs_description,obs_copyright,obs_regime,cs_service_url,hips_service_url",
};
this._allCatalogHiPSes = Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {data: params, dataType: 'json'})

View File

@@ -624,12 +624,13 @@ export let ProgressiveCat = (function() {
},
computeFootprints: Catalog.prototype.computeFootprints,
setSourceSize: Catalog.prototype.setSourceSize,
setShape: Catalog.prototype.setShape,
setColor: Catalog.prototype.setColor,
reportChange: function() { // TODO: to be shared with Catalog
this.view && this.view.requestRedraw();
}
}; // END OF .prototype functions
ProgressiveCat.parser = new DOMParser();

View File

@@ -34,20 +34,20 @@ export let ProjectionEnum = {
SIN: {id: 3, label: "Spheric"}, /* Orthographic */
// TODO: fix why the projection disappears at fov = 360.0
ZEA: {id: 4, label: "Zenital equal-area"}, /* Equal-area */
//FEYE: {id: 5, fov: 190, label: "fish eye"},
//AIR: {id: 6, fov: 360, label: "airy"},
/*FEYE: {id: 5, fov: 190, label: "fish eye"},
AIR: {id: 6, fov: 360, label: "airy"},
//AZP: {fov: 180},
//ARC: {id: 7, fov: 360, label: "zenital equidistant"},
//NCP: {id: 8, fov: 180, label: "north celestial pole"},
ARC: {id: 7, fov: 360, label: "zenital equidistant"},
NCP: {id: 8, fov: 180, label: "north celestial pole"},*/
// Cylindrical
MER: {id: 9, label: "Mercator"},
//CAR: {id: 10, fov: 360, label: "plate carrée"},
//CEA: {id: 11, fov: 360, label: "cylindrical equal area"},
//CYP: {id: 12, fov: 360, label: "cylindrical perspective"},
/*CAR: {id: 10, fov: 360, label: "plate carrée"},
CEA: {id: 11, fov: 360, label: "cylindrical equal area"},
CYP: {id: 12, fov: 360, label: "cylindrical perspective"},*/
// Pseudo-cylindrical
AIT: {id: 13, label: "Hammer-Aïtoff"},
//PAR: {id: 14, fov: 360, label: "parabolic"},
//SFL: {id: 15, fov: 360, label: "sanson-flamsteed"},
/*PAR: {id: 14, fov: 360, label: "parabolic"},
SFL: {id: 15, fov: 360, label: "sanson-flamsteed"},*/
MOL: {id: 16, label: "Mollweide"},
// Conic
//COD: {id: 17, fov: 360, label: "conic equidistant"},

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,10 +21,9 @@ 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";
import { DOMElement } from "./gui/Widgets/Widget";
/******************************************************************************
* Aladin Lite project
*
@@ -35,7 +34,7 @@ import { Aladin } from "./Aladin";
*
*****************************************************************************/
export class SpectraDisplayer {
export class SpectraDisplayer extends DOMElement {
static UNIT = {
FREQUENCY: {
label: "f",
@@ -119,6 +118,8 @@ export class SpectraDisplayer {
};
constructor(view, options) {
super()
let createPlotCanvas = (name) => {
const canvas = document.createElement("canvas");
canvas.classList.add(name);
@@ -142,6 +143,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 +171,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 +248,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 +259,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 +307,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 +339,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 +407,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 +416,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 +443,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', {
@@ -440,67 +557,9 @@ export class SpectraDisplayer {
this.view.catalogCanvas.dispatchEvent(wheelEvent);
});
/*
const updateSelectorList = () => {
let options = [];
for (const hipsName of this.hips3DList.keys()) {
options.push(hipsName)
}
this.selector.update({options})
};
ALEvent.HIPS_LAYER_ADDED.listenedBy(
this.view.aladin.aladinDiv,
function (e) {
let hips = e.detail.layer;
if (hips.dataproductType === "spectral-cube") {
self.hips3DList.set(hips.name, hips);
updateSelectorList()
}
}
);
ALEvent.HIPS_LAYER_SWAP.listenedBy(
this.view.aladin.aladinDiv,
function (e) {
let firstHiPS = e.detail.firstLayer;
let secondHiPS = e.detail.secondLayer;
self.hips3DList.delete(firstHiPS.name);
if (secondHiPS.dataproductType === "spectral-cube") {
self.hips3DList.set(secondHiPS.name, secondHiPS);
}
updateSelectorList()
}
);
ALEvent.HIPS_LAYER_REMOVED.listenedBy(
this.view.aladin.aladinDiv,
function (e) {
let hips = e.detail.layer;
self.hips3DList.delete(hips.name);
if (hips === this.hips) {
// the hips pointed by the tool has been removed
self.attachHiPS3D(null);
}
if (self.hips3DList.size === 0) {
self.hide()
}
updateSelectorList()
}
);*/
}
hide() {
_hide() {
if (this.isHidden) {
return;
}
@@ -509,7 +568,7 @@ export class SpectraDisplayer {
this.isHidden = true;
}
show() {
_show() {
if (!this.isHidden) {
return;
}
@@ -535,11 +594,11 @@ export class SpectraDisplayer {
this._redraw(this.ctx);
}
};
window.addEventListener("spectra", this.spectraUpdateCallback);
this.resetScale();
this.show()
this._show()
}
}
@@ -550,10 +609,12 @@ export class SpectraDisplayer {
}
enableInteraction() {
this.enabled = true;
this.divNode.style.pointerEvents = "auto"
}
disableInteraction() {
this.enabled = false;
this.divNode.style.pointerEvents = "none"
}
@@ -591,6 +652,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 +774,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 +812,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

@@ -48,6 +48,7 @@ import { HiPS } from "./HiPS.js";
import { Image } from "./Image.js";
import { Color } from "./Color.js";
import { SpectraDisplayer } from "./SpectraDisplayer.js";
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
export let View = (function () {
@@ -93,7 +94,7 @@ export let View = (function () {
// 1. Print the original exception message in the console
//console.error(e)
// 2. Add a more explicite message to the end user
console.error("Problem initializing Aladin Lite. Please contact the support by contacting Matthieu Baumann (baumannmatthieu0@gmail.com) or Thomas Boch (thomas.boch@astro.unistra.fr). You can also open an issue on the Aladin Lite github repository here: https://github.com/cds-astro/aladin-lite. Message error:" + e)
console.error("Problem initializing Aladin Lite. Please contact the support by contacting Matthieu Baumann (matthieu.baumann@astro.unistra.fr) or Thomas Boch (thomas.boch@astro.unistra.fr). You can also open an issue on the Aladin Lite github repository here: https://github.com/cds-astro/aladin-lite. Message error:" + e)
}
this._defineProperties();
@@ -256,7 +257,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;
@@ -276,7 +277,6 @@ export let View = (function () {
colorPickerElement = document.createElement('span');
colorPickerElement.classList.add('aladin-color-picker')
colorPickerElement.classList.add('aladin-view-label')
colorPickerElement.classList.add('aladin-dark-theme')
this.aladin.aladinDiv.appendChild(colorPickerElement);
}
@@ -596,6 +596,9 @@ export let View = (function () {
return;
}
if (this.spectraDisplayer)
this.spectraDisplayer._hide();
if (imageLayer.dataproductType === "spectral-cube") {
if (!this.spectraDisplayer) {
this.spectraDisplayer = new SpectraDisplayer(this, {width: 800, height: 300});
@@ -785,17 +788,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 +821,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
@@ -864,7 +836,7 @@ export let View = (function () {
.then(() => {
if (view.aladin.statusBar) {
view.aladin.statusBar.appendMessage({
message: `${view.colorPickerTool.probedValue} copied into your clipboard`,
message: `<span class="aladin-indicator" style="background-color: ${view.colorPickerTool.probedValue}"></span> [${view.colorPickerTool.probedValue}] copied into your clipboard`,
duration: 1500,
type: 'info'
})
@@ -875,6 +847,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);
@@ -913,28 +907,11 @@ export let View = (function () {
return;
}
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 (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) ... "
var wasDragging = view.realDragging === true;
view.mustClearCatalog = true;
view.dragCoo = null;
view.dragPastCoo = null;
if (e.type === "mouseup") {
if (view.mode === View.SELECT) {
@@ -961,8 +938,13 @@ export let View = (function () {
}
if (view.rightClick) {
if (showContextMenu) {
view.aladin.contextMenu && view.aladin.contextMenu.show({e});
let ctxMenu = view.aladin.contextMenu;
if (showContextMenu && ctxMenu) {
ctxMenu.attach(
DefaultActionsForContextMenu.getDefaultActions(view.aladin),
null
);
ctxMenu._show({e});
}
view.rightClick = false;
@@ -1002,7 +984,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 +1233,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 +1286,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 +1366,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 +1416,9 @@ export let View = (function () {
this.drawAllOverlays();
}
this.needRedraw = false;
// request another frame
requestAnimFrame(this.redrawClbk);
};
View.prototype.drawAllOverlays = function () {
@@ -1686,6 +1696,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 +1774,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.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;
@@ -1768,36 +1803,15 @@ export let View = (function () {
// Insert a layer object (Image/HiPS) at a specific index in the stack
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);
}
const layer = imageLayer.layer;
imageLayer.added = true;
this.imageLayers.set(layerName, imageLayer);
this.imageLayers.set(layer, imageLayer);
// select the layer if he is on top
//if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
//}
this.selectLayer(layer);
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
ALEvent.LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
View.prototype.addImageLayer = function (imageLayer, layer) {
@@ -1810,7 +1824,7 @@ export let View = (function () {
// All image layer promises must be completed (fullfilled or rejected)
const task = {
message: 'Load layer: ' + imageLayer.name,
message: imageLayer.name + ' loading...',
id: Utils.uuidv4(),
}
// Ensure all the properties for HiPSes have been seeked
@@ -1819,7 +1833,7 @@ export let View = (function () {
// so that we can add it to the view (call of _add2View)
Promise.all([Promise.allSettled(this.promises), imageLayerPromise])
// Then we add the layer to the view
.then((_) => imageLayer._add2View(layer))
.then((_) => imageLayer._addToView(layer))
// Then we keep a track of the layer in the JS front
.then((imageLayer) => {
this._addLayer(imageLayer);
@@ -1852,66 +1866,29 @@ export let View = (function () {
// Remove the settled promise
this.promises.splice(idx, 1);
const noMoreLayersToWaitFor = this.promises.length === 0;
if (noMoreLayersToWaitFor) {
if (self.empty) {
// no promises to launch and the view has no HiPS.
// This situation can occurs if the MOCServer is out
// If so we can directly put the url of the DSS hosted in alasky,
// 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");
}
}
})
}
// The survey at layer must have been added to the view!
View.prototype.renameLayer = function(layer, newLayer) {
if (layer === newLayer) {
return;
}
View.prototype.swapLayers = function(layer1, layer2) {
// 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);
this.wasm.swapLayers(layer1, layer2);
// Swap in overlaylayers
const idxFirstLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == firstLayer);
const idxSecondLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == secondLayer);
const i = this.overlayLayers.indexOf(layer1);
const j = this.overlayLayers.indexOf(layer2);
const tmp = this.overlayLayers[idxFirstLayer];
this.overlayLayers[idxFirstLayer] = this.overlayLayers[idxSecondLayer];
this.overlayLayers[idxSecondLayer] = tmp;
const tmp = this.overlayLayers[i];
this.overlayLayers[i] = this.overlayLayers[j];
this.overlayLayers[j] = tmp;
// Tell the layer hierarchy has changed
ALEvent.HIPS_LAYER_SWAP.dispatchedTo(this.aladinDiv, { firstLayer: firstLayer, secondLayer: secondLayer });
ALEvent.LAYER_SWAPPED.dispatchedTo(
this.aladinDiv,
{
layer1: this.imageLayers.get(layer1),
layer2: this.imageLayers.get(layer2)
}
);
}
View.prototype.removeImageLayer = function (layer) {
@@ -1924,9 +1901,7 @@ export let View = (function () {
}
// Update the backend
if (imageLayer.added) {
this.wasm.removeLayer(layer);
}
imageLayer._removeFromView();
// Get the survey to remove to dissociate it from the view
imageLayer.added = false;
@@ -1943,21 +1918,13 @@ 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]);
}
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
// check if there are no more surveys
const noMoreLayersToWaitFor = this.promises.length === 0;
if (noMoreLayersToWaitFor && this.empty) {
// no promises to launch!
const dssId = Aladin.DEFAULT_OPTIONS.survey;
this.aladin.setBaseImageLayer(dssId);
}
ALEvent.LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
};
View.prototype.contains = function(survey) {
@@ -2004,7 +1971,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

@@ -51,17 +51,14 @@ export class ALEvent {
static POSITION_CHANGED = new ALEvent("AL:position.changed");
static ZOOM_CHANGED = new ALEvent("AL:zoom.changed");
static HIPS_LAYER_ADDED = new ALEvent("AL:HiPSLayer.added");
static HIPS_LAYER_REMOVED = new ALEvent("AL:HiPSLayer.removed");
static HIPS_LAYER_RENAMED = new ALEvent("AL:HiPSLayer.renamed");
static HIPS_LAYER_SWAP = new ALEvent("AL:HiPSLayer.swap");
static HIPS_LAYER_CHANGED = new ALEvent("AL:HiPSLayer.changed");
static LAYER_ADDED = new ALEvent("AL:Layer.added");
static LAYER_REMOVED = new ALEvent("AL:Layer.removed");
static LAYER_SWAPPED = new ALEvent("AL:Layer.swapped");
static LAYER_CHANGED = new ALEvent("AL:Layer.changed");
static HIPS_CACHE_UPDATED = new ALEvent("AL:HiPSCache.updated");
static FAVORITE_HIPS_LIST_UPDATED = new ALEvent("AL:HiPSFavorites.updated");
static GRAPHIC_OVERLAY_LAYER_ADDED = new ALEvent("AL:GraphicOverlayLayer.added");
static GRAPHIC_OVERLAY_LAYER_REMOVED = new ALEvent("AL:GraphicOverlayLayer.removed");

View File

@@ -51,7 +51,7 @@ import { ActionButton } from "../Widgets/ActionButton.js";
CatalogQueryBox.catalogs[cat.obs_title] = cat;
});
inputText.update({autocomplete: {options: Object.keys(CatalogQueryBox.catalogs)}})
searchDropdown.update({options: Object.keys(CatalogQueryBox.catalogs)})
})
const fnIdSelected = function(type, params) {
@@ -120,12 +120,12 @@ import { ActionButton } from "../Widgets/ActionButton.js";
self.fnIdSelected('votable', {
url: votableUrl,
success: () => {
inputText.addClass('aladin-valid');
searchDropdown.addClass('aladin-valid');
},
error: () => {
inputText.addClass('aladin-not-valid')
self.csForm.submit.update({disable: true})
self.hipsCatLoad.update({disable: true});
searchDropdown.addClass('aladin-not-valid')
self.csForm.submit.update({disabled: true})
self.hipsCatLoad.update({disabled: true});
}
})
} catch (e) {
@@ -134,24 +134,24 @@ import { ActionButton } from "../Widgets/ActionButton.js";
if (catalog) {
self._selectItem(catalog, aladin);
inputText.addClass('aladin-valid');
searchDropdown.addClass('aladin-valid');
} else {
// consider it as a cat ID and search in catalogs for it
const foundCat = Object.values(CatalogQueryBox.catalogs)
.find((c) => c.ID === value);
if (foundCat) {
self._selectItem(foundCat, aladin);
inputText.addClass('aladin-valid')
searchDropdown.addClass('aladin-valid')
} else {
inputText.addClass('aladin-not-valid')
self.csForm.submit.update({disable: true})
self.hipsCatLoad.update({disable: true});
searchDropdown.addClass('aladin-not-valid')
self.csForm.submit.update({disabled: true})
self.hipsCatLoad.update({disabled: true});
}
}
}
}
let inputText = new Dropdown(aladin, {
let searchDropdown = new Dropdown(aladin, {
name: 'catalogs',
placeholder: "Type ID, title, keyword or URL",
tooltip: {
@@ -159,22 +159,9 @@ import { ActionButton } from "../Widgets/ActionButton.js";
aladin,
content: 'HiPS url, ID or keyword accepted',
},
actions: {
input(e) {
inputText.removeClass('aladin-valid')
inputText.removeClass('aladin-not-valid')
},
focus(e) {
inputText.removeClass('aladin-valid')
inputText.removeClass('aladin-not-valid')
},
change(e) {
e.stopPropagation();
e.preventDefault()
_parseEntry(e)
},
},
action: (e) => {
_parseEntry(e)
}
});
let self;
@@ -190,7 +177,7 @@ import { ActionButton } from "../Widgets/ActionButton.js";
position: {direction: "bottom"}
},
content: 'HiPS',
disable: true,
disabled: true,
action() {
self.fnIdSelected('hips', {
hipsURL: self.selectedItem.hips_service_url,
@@ -220,7 +207,6 @@ import { ActionButton } from "../Widgets/ActionButton.js";
let [lon, lat] = coo.format('s2');
let fov = new Angle(radius, 1).format();
//selectorBtn.update({tooltip: {content: 'center: ' + ra.toFixed(2) + ', ' + dec.toFixed(2) + '<br\>radius: ' + radius.toFixed(2), position: {direction: 'left'}}})
form.set('ra', lon)
form.set('dec', lat)
form.set('rad', fov)
@@ -241,7 +227,7 @@ import { ActionButton } from "../Widgets/ActionButton.js";
let form = new Form({
submit: {
disable: true,
disabled: true,
icon: {
monochrome: true,
url: targetIconUrl,
@@ -344,20 +330,25 @@ import { ActionButton } from "../Widgets/ActionButton.js";
classList: ['aladin-cat-browser-box'],
content: Layout.vertical(
[
Layout.horizontal({
layout: ["Search:", inputText], cssStyle: {width: '100%'}
}),
Layout.horizontal({
layout: ["Progressive catalog:", hipsCatLoad],
cssStyle: {
textAlign: "center",
display: "flex",
alignItems: "center",
listStyle: "none",
justifyContent: "space-between",
width: "100%",
},
}),
Layout.horizontal(
["Search:", searchDropdown],
{cssStyle:
{width: '100%'}
}
),
Layout.horizontal(
["Progressive catalog:", hipsCatLoad],
{
cssStyle: {
textAlign: "center",
display: "flex",
alignItems: "center",
listStyle: "none",
justifyContent: "space-between",
width: "100%",
},
}
),
form
]
),
@@ -367,7 +358,7 @@ import { ActionButton } from "../Widgets/ActionButton.js";
self = this;
this.hipsCatLoad = hipsCatLoad;
this.csForm = form;
this.inputText = inputText;
this.searchDropdown = searchDropdown;
this.fnIdSelected = fnIdSelected;
}
@@ -375,15 +366,15 @@ import { ActionButton } from "../Widgets/ActionButton.js";
this.selectedItem = item;
if (!item) {
this.csForm.submit.update({disable: true})
this.hipsCatLoad.update({disable: true});
this.csForm.submit.update({disabled: true})
this.hipsCatLoad.update({disabled: true});
} else {
if (item && item.cs_service_url) {
this.csForm.submit.update({disable: false});
this.csForm.submit.update({disabled: false});
}
if (item && item.hips_service_url) {
this.hipsCatLoad.update({disable: false});
this.hipsCatLoad.update({disabled: false});
}
}
}

View File

@@ -36,20 +36,6 @@ import { SearchTextInput } from "../Input/InputTextSearch.js";
export class GotoBox extends Box {
// Constructor
constructor(aladin) {
/*let content = Layout.horizontal([
'Go to:',
Input.text({
//tooltip: {content: 'Search for a VizieR catalogue', position: {direction :'bottom'}},
label: "Go to:",
name: "goto",
type: "text",
placeholder: 'Object name/position',
autocomplete: 'off',
change(e, self) {
self.addEventListener('blur', (event) => {});
}
})
]);*/
let textField = new SearchTextInput(aladin, {
cssStyle: {
width: '15rem'

View File

@@ -41,6 +41,7 @@ import labelSizeIcon from './../../../../assets/icons/font-size.svg';
export class GridBox extends Box {
// Constructor
constructor(aladin) {
let self;
let colorInput = new Input({
layout: {
name: 'gridColor',
@@ -98,7 +99,7 @@ export class GridBox extends Box {
})
}
ctxMenu.attach(ctxMenuLayout);
ctxMenu.attach(ctxMenuLayout, self);
ctxMenu.show({
e: e,
position: {
@@ -134,7 +135,7 @@ export class GridBox extends Box {
})
}
ctxMenu.attach(ctxMenuLayout);
ctxMenu.attach(ctxMenuLayout, self);
ctxMenu.show({
e: e,
position: {
@@ -154,15 +155,13 @@ export class GridBox extends Box {
}
});
sliderOpacity.addClass("aladin-input-range")
const layout = Layout.horizontal({
layout: [
enableCheckbox,
labelSizeBtn,
thicknessLineBtn,
colorInput,
sliderOpacity
]
})
const layout = Layout.horizontal([
enableCheckbox,
labelSizeBtn,
thicknessLineBtn,
colorInput,
sliderOpacity
])
layout.addClass('aladin-grid-frame');
@@ -182,6 +181,7 @@ export class GridBox extends Box {
this.addClass("aladin-box-night")
this.aladin = aladin;
self = this;
this._hide();
}

View File

@@ -22,17 +22,20 @@ import { MocServer } from "../../MocServer.js";
import { Box } from "../Widgets/Box.js";
import { Dropdown } from "../Input/Dropdown.js";
import filterOnUrl from "../../../../assets/icons/filter-on.svg";
import hipsIconUrl from "../../../../assets/icons/hips.svg";
import treeIconUrl from "../../../../assets/icons/tree.svg";
import filterOffUrl from "../../../../assets/icons/filter-off.svg";
import helpIconUrl from "../../../../assets/icons/help.svg";
import { Input } from "../Widgets/Input.js";
import { TogglerActionButton } from "../Button/Toggler.js";
import { WidgetTogglerButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
import { HiPSFilterBox } from "./HiPSFilterBox.js";
import A from "../../A.js";
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 +47,143 @@ import { Icon } from "../Widgets/Icon.js";
*
*****************************************************************************/
function fillHiPSHierarchy(name, hips, path, hierarchy) {
if (path[path.length - 1] === '/') {
path = path.substring(0, path.length - 1);
}
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 && !item.ID)
return false;
let obsTitleDoesNotMatch = !item.obs_title.toLowerCase().includes(params.title.toLowerCase());
let creatorDidDoesNotMatch = !item.ID.toLowerCase().includes(params.title.toLowerCase());
if (obsTitleDoesNotMatch && creatorDidDoesNotMatch) {
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)
},
dblclick: (item) => {
let image = item.ID || item.hips_service_url;
let name = item.obs_title || item.ID;
self._addHiPS(image, name)
self.close();
},
// a callback called for filtering
filter,
aladin,
});
MocServer.getAllHiPSes().then((HiPSes) => {
HiPSBrowserBox.HiPSList = {}
let hipsHierarchy = {};
// Fill the HiPSList from the MOCServer
// Build a hierarchy w.r.t sorted by regime
let HiPSIDs = []
HiPSes.forEach((h) => {
let name = h.obs_title;
name = name.replace(/:|\'/g, '');
HiPSIDs.push(name)
HiPSBrowserBox.HiPSList[name] = h;
if (h.client_category) {
let path = h.client_category
fillHiPSHierarchy(name, h, path, hipsHierarchy)
} else {
let hipsID = h.ID;
hipsID = hipsID.replace('/P', '');
let path = "Others/" + hipsID;
fillHiPSHierarchy(name, h, path, hipsHierarchy)
}
});
self.searchDropdown.update({ options: HiPSIDs });
self.searchTree.setHierarchy(hipsHierarchy)
// Initialize the autocompletion without any filtering
self._filterHiPSList({})
});
const _parseHiPS = (e) => {
const value = e.target.value;
@@ -88,7 +207,6 @@ export class HiPSBrowserBox extends Box {
if (image) {
self._addHiPS(image, name)
self.searchDropdown.update({title: value});
}
};
@@ -98,39 +216,24 @@ export class HiPSBrowserBox extends Box {
tooltip: {
global: true,
aladin,
content: 'HiPS url, ID or keyword accepted',
content: 'HiPS url, ID or keyword accepted.',
},
actions: {
focus(e) {
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
keydown(e) {
e.stopPropagation();
action: (e) => {
_parseHiPS(e)
},
input: (e) => {
let value = e.target.value;
self.searchTree.triggerFilter({title: value});
if (e.key === 'Enter') {
e.preventDefault()
_parseHiPS(e)
}
},
input(e) {
self.infoCurrentHiPSBtn.update({
disable: true,
})
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
change(e) {
e.stopPropagation();
e.preventDefault()
_parseHiPS(e)
}
self.infoCurrentHiPSBtn.update({
disabled: true,
})
},
});
let filterEnabler = Input.checkbox({
name: "filter-enabler",
tooltip: { content: "Enable the filter" },
checked: false,
click(e) {
let on = e.target.checked;
@@ -156,21 +259,15 @@ export class HiPSBrowserBox extends Box {
},
});
let infoCurrentHiPSBtn = new ActionButton({
disable: true,
icon: {
size: 'medium',
monochrome: true,
url: infoIconUrl,
},
tooltip: {
global: true,
aladin,
content: "More about that survey?"
}
});
let infoCurrentHiPSBtn = ActionButton.BUTTONS(aladin)
.infoHiPS({disabled: true})
let filterBtn = new TogglerActionButton({
let filterBox = new HiPSFilterBox(aladin, {
callback: (params) => {
self._filterHiPSList(params);
},
})
let filterBtn = new WidgetTogglerButton({
icon: {
url: filterOffUrl,
monochrome: true,
@@ -181,52 +278,59 @@ export class HiPSBrowserBox extends Box {
position: { direction: "top" },
},
toggled: false,
actionOn: (e) => {
self.filterBox._show({
position: {
nextTo: filterBtn,
direction: "bottom",
aladin,
},
});
},
actionOff: (e) => {
self.filterBox._hide();
},
openPosition: 'right center',
widget: filterBox,
});
let filterNumberElt = document.createElement("div");
super(
{
super({
close: true,
tooltip: {
global: true,
aladin,
content: 'orange: out of the view, green: in view'
},
header: {
title: Layout.horizontal([new Icon({
size: 'medium',
url: hipsIconUrl,
monochrome: true,
}), "HiPS browser"])
},
onDragged: () => {
if (self.filterBtn.toggled) {
self.filterBtn.toggle();
}
title: [
new Icon({
size: 'medium',
url: treeIconUrl,
monochrome: true,
}),
"HiPS browser",
new Icon({
size: 'medium',
url: helpIconUrl,
monochrome: true,
tooltip: {
content: 'HiPS:<br/><span class="aladin-indicator aladin-not-found"></span> out of the view<br /><span class="aladin-indicator aladin-valid"></span> in view',
mouse: true,
aladin
},
style: {
cursor: 'help'
}
}),
],
draggable: true,
},
sizeable: true,
classList: ['aladin-HiPS-browser-box'],
content: Layout.vertical([
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn, filterNumberElt])]),
]),
content: [
searchTree,
["Search:", searchDropdown, infoCurrentHiPSBtn],
[filterEnabler, filterBtn, filterNumberElt],
],
...options,
},
aladin.aladinDiv
);
this.filterBox = new HiPSFilterBox(aladin, {
callback: (params) => {
self._filterHiPSList(params);
},
})
self = this;
this.searchTree = searchTree;
this.filterBox = filterBox;
this.filterNumberElt = filterNumberElt;
this.filterBox._hide();
@@ -236,46 +340,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) => {
@@ -283,7 +383,7 @@ export class HiPSBrowserBox extends Box {
self.searchDropdown.addClass('aladin-valid');
self.infoCurrentHiPSBtn.update({
disable: false,
disabled: false,
action(e) {
window.open(hips.url);
}
@@ -389,37 +489,38 @@ export class HiPSBrowserBox extends Box {
self.searchDropdown.addClass('aladin-not-valid');
}
});
this.aladin.setOverlayImageLayer(hips, self.layer);
self.selected(hips)
}
// This method is executed only if the filter is enabled
_filterHiPSList(params) {
let self = this;
let HiPSIDs = [];
let numHiPSMatching = 0;
for (var key in HiPSBrowserBox.HiPSList) {
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;
name = name.replace(/:|\'/g, "");
HiPSIDs.push(name);
numHiPSMatching += 1;
}
}
self.searchDropdown.update({ options: HiPSIDs });
self.filterNumberElt.innerHTML = HiPSIDs.length + "/" + Object.keys(HiPSBrowserBox.HiPSList).length;
if (self.searchTree) {
self.searchTree.triggerFilter(params);
}
self.filterNumberElt.innerHTML = numHiPSMatching + "/" + Object.keys(HiPSBrowserBox.HiPSList).length;
}
_hide() {
if (this.filterBox)
this.filterBox.signalBrowserStatus(true)
if (this.filterBtn && this.filterBtn.toggled) {
this.filterBtn.toggle();
}
@@ -429,11 +530,10 @@ export class HiPSBrowserBox extends Box {
_show(options) {
// Regenerate a new layer name
this.layer = Utils.uuidv4()
if (this.filterBox)
this.filterBox.signalBrowserStatus(false)
this.selected = options && options.selected;
super._show(options)
this._requestMOCServer();
}
}

View File

@@ -0,0 +1,267 @@
// Copyright 2013 - 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 { Box } from "../Widgets/Box.js";
import hipsIconUrl from "../../../../assets/icons/hips.svg";
import addIconUrl from "../../../../assets/icons/plus.svg";
import settingsIconUrl from "../../../../assets/icons/settings.svg";
import { Icon } from "../Widgets/Icon.js";
import { HiPSSelector } from "../Input/HiPSSelector.js";
import { HiPSBrowserBox } from "./HiPSBrowserBox.js";
import { Input } from "../Widgets/Input.js";
import { ActionButton } from "../Widgets/ActionButton.js";
import { Form } from "../Widgets/Form.js";
import { TogglerActionButton } from "../Button/Toggler.js";
import { Utils } from "../../Utils";
import { HiPSComposite } from "../../HiPSComposite.js";
/******************************************************************************
* Aladin Lite project
*
* File gui/Box/HiPSCompositeBox.js
*
* The code source of the interface for creating a new composite HiPS survey from multiple surveys
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
export class HiPSCompositeBox extends Box {
static HiPSList = {};
constructor(aladin, options) {
let self;
let nameInput = Input.text({
tooltip: {
global: true,
aladin,
content: 'What name for your composite survey?'
},
placeholder: "What name?...",
autocomplete: 'off',
autofocus: true,
actions: {
dblclick: (_) => {
nameInput.set('')
},
keydown: (e) => {
e.stopPropagation();
//
}
},
});
let content = [[
new ActionButton({
icon: {
url: addIconUrl,
size: "small",
monochrome: true,
},
tooltip: {
content: "Add a new layer",
position: { direction: "top" },
},
toggled: false,
action(_) {
self.content.push(self._addNewHiPS());
self.update({content: self.content})
}
}),
nameInput
]];
super(
{
close: true,
header: {
title: [
new Icon({
size: 'medium',
url: hipsIconUrl,
monochrome: true,
}),
"HiPS Compositor"
],
draggable: true,
},
content,
...options,
},
aladin.aladinDiv
);
this.aladin = aladin;
this.hipsOptions = [];
self = this;
this.layer = Utils.uuidv4();
this.hipsComposite = new HiPSComposite(this.hipsOptions)
this.numHiPSLayers = 0;
this.content = content.concat([this._addNewHiPS()])
this.update({content: this.content})
this.openSettings = null;
}
_addNewHiPS() {
const getIdHiPS = (node) => {
let parent = node.parentElement;
return [...parent.parentElement.children].indexOf(parent) - 1;
};
this.hipsOptions.push({});
let self = this;
let newLayerLayout = [
new HiPSSelector({
change(e) {
let name = e.target.value;
let idLayer = getIdHiPS(e.target);
if (name === "More...") {
if (!aladin.hipsBrowser) {
aladin.hipsBrowser = new HiPSBrowserBox(aladin);
}
aladin.hipsBrowser._show({
selected: (hips) => {
self.hipsOptions[idLayer].id = hips.id || hips.url
},
position: { anchor: "center center" }
});
} else {
// it is an hips
let HiPSOptions = HiPSSelector.cachedHiPS[name];
self.hipsOptions[idLayer].id = HiPSOptions.id || HiPSOptions.url
}
self.hipsComposite.setOptions(self.hipsOptions);
self.aladin.setOverlayImageLayer(self.hipsComposite, self.layer);
}
}),
this._createLayerSettingsBox(),
ActionButton.BUTTONS(aladin)
.remove((e) => {
let node = e.target.parentElement.parentElement.parentElement;
let idLayer = [...node.parentElement.children].indexOf(node);
this.content.splice(idLayer, 1)
this.hipsOptions.splice(idLayer, 1);
this.update({content: this.content})
this.numHiPSLayers = this.content.length - 1;
})
];
this.numHiPSLayers += 1;
return newLayerLayout;
}
_createLayerSettingsBox() {
let self = this;
let layerSettingsBox = new Box({
close: false,
content: new Form({
subInputs: [
{
type: 'color',
label: "Color",
value: 'red',
name: 'color',
change(e) {
let idLayer = getIdHiPS(e.target);
let hex = e.target.value;
}
},
{
label: 'Stretch',
type: "select",
name: 'stretch',
value: 'linear',
options: ['sqrt', 'linear', 'asinh', 'pow2', 'log'],
change(e) {},
},
{
type: 'number',
label: "Min cut",
name: 'mincut',
value: 0.0,
change: (e) => {
let minCut = +e.target.value
}
},
{
label: 'Max cut',
type: "number",
name: 'maxcut',
value: 0.0,
change: (e) => {
let maxCut = +e.target.value
}
},
]
}),
}, this.aladin.aladinDiv);
layerSettingsBox._hide()
/*let layerSettingsBtn = new TogglerActionButton({
icon: { url: settingsIconUrl, monochrome: true },
size: "small",
tooltip: {
content: "Settings",
position: { direction: "top" },
},
toggled: false,
on: (_) => {
layerSettingsBox._show({
position: {
nextTo: layerSettingsBtn,
direction: "right",
aladin: self.aladin,
},
});
if (self.openSettings) {
self.openSettings.close();
}
self.openSettings = layerSettingsBtn;
},
off: (_) => {
layerSettingsBox._hide();
if (self.openSettings === layerSettingsBtn) {
self.openSettings = null;
}
},
});*/
return layerSettingsBtn
}
_hide() {
if (this.openSettings)
this.openSettings.close();
super._hide()
}
}

View File

@@ -17,19 +17,12 @@
// along with Aladin Lite.
//
import filterOnUrl from "../../../../assets/icons/filter-on.svg";
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';
import { Icon } from "../Widgets/Icon.js";
/******************************************************************************
* Aladin Lite project
@@ -45,185 +38,127 @@ 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: 'enable/disable', 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: 'enable/disable', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
self._triggerFilteringCallback();
}
});
let logSlider = new Input({
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'}}});
let resolution = new Angle(deg);
self.params["resolution"] = resolution.degrees();
self._triggerFilteringCallback();
},
});
super(
{
classList: ['aladin-HiPS-filter-box'],
header: {
title: [
new Icon({
size: 'medium',
url: filterOnUrl,
monochrome: true,
}),
'Filter'
],
draggable: false,
},
close: false,
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
classList: ['aladin-HiPS-filter-box'],
content: [
{
start: [
"Freq:",
Input.select({
tooltip: {
content: "Observation regime",
position: { direction: "left" },
},
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();
},
}),
],
}),
])
end: [regimeBtn]
},
{
start: [
"Max resolution [°/px]:",
new Input({
name: "res",
value: 0.1,
type: 'range',
cssStyle: {
width: '200px'
},
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
ticks: [0.001 / 3600, 0.01 / 3600, 0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
stretch: "log",
min: 0.001 / 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();
self._triggerFilteringCallback();
},
})
],
end: [resolutionBtn]
}
]
},
aladin.aladinDiv
);
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 +167,9 @@ export class HiPSFilterBox extends Box {
}
}
signalBrowserStatus(closed) {
this.browserClosed = closed;
// open
if (!closed) {
this._requestMOCServer()
}
}
enable(enable) {
this.on = enable;
if (this.on)
this._requestMOCServer();
this._triggerFilteringCallback();
}
}

View File

@@ -1,172 +0,0 @@
// Copyright 2013 - 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 { MocServer } from "../../MocServer.js";
import { Box } from "../Widgets/Box.js";
import { Layout } from "../Layout.js";
import { ActionButton } from "../Widgets/ActionButton.js";
import { Input } from "../Widgets/Input.js";
/******************************************************************************
* Aladin Lite project
*
* File gui/HiPSSelector.js
*
*
* Author: Thomas Boch, Matthieu Baumann[CDS]
*
*****************************************************************************/
export class HiPSSelectorBox extends Box {
static HiPSList = {};
constructor(aladin, options) {
MocServer.getAllHiPSes()
.then((HiPSes) => {
HiPSes.forEach((h) => {
HiPSSelectorBox.HiPSList[h.obs_title] = h
HiPSSelectorBox.HiPSList[h.ID] = h
});
inputText.update({autocomplete: {options: Object.keys(HiPSSelectorBox.HiPSList)}})
});
let self;
let loadBtn = new ActionButton({
content: 'Add',
disable: true,
action(e) {
self.callback && self.callback(inputText.get());
// reset the field
inputText.set('');
self._hide();
}
})
let inputText = Input.text({
classList: ['search'],
name: 'survey',
placeholder: "Type survey keywords",
actions: {
change() {
const HiPS = HiPSSelectorBox.HiPSList[this.value];
inputText.set(HiPS.ID);
loadBtn.update({disable: false});
},
keydown() {
loadBtn.update({disable: true});
}
}
});
super(
{
close: false,
content: Layout.horizontal({
layout: [
inputText,
loadBtn
]
}),
...options
},
aladin.aladinDiv
)
self = this;
// Query the mocserver
/*MocServer.getAllHiPSes();
autocomplete({
input: inputText.element(),
fetch: function(text, update) {
text = text.toLowerCase();
// filter suggestions
const suggestions = MocServer.getAllHiPSes().filter(n => n.ID.toLowerCase().includes(text) || n.obs_title.toLowerCase().includes(text))
// sort suggestions
suggestions.sort( function(a , b) {
let scoreForA = 0;
let scoreForB = 0;
if (a.ID.toLowerCase().includes(text)) {
scoreForA += 100;
}
if (b.ID.toLowerCase().includes(text)) {
scoreForB += 100;
}
if (a.obs_title.toLowerCase().includes(text)) {
scoreForA += 50;
}
if (b.obs_title.toLowerCase().includes(text)) {
scoreForB += 50;
}
if (a.obs_description && a.obs_description.toLowerCase().includes(text)) {
scoreForA += 10;
}
if (b.obs_description && b.obs_description.toLowerCase().includes(text)) {
scoreForB += 10;
}
if (scoreForA > scoreForB) {
return -1;
}
if (scoreForB > scoreForA) {
return 1;
}
return 0;
});
// limit to 50 first suggestions
const returnedSuggestions = suggestions.slice(0, 50);
update(returnedSuggestions);
},
onSelect: function(item) {
inputText.set(item.ID);
loadBtn.update({disable: false});
inputText.element().blur();
},
// attach container to AL div if needed (to prevent it from being hidden in full screen mode)
customize: function(input, inputRect, container, maxHeight) {
// this tests if we are in full screen mode
if (aladin.isInFullscreen) {
aladin.aladinDiv.appendChild(container);
}
},
render: function(item, currentValue) {
const itemElement = document.createElement("div");
itemElement.innerHTML = item.obs_title + ' - ' + '<span style="color: #ae8de1">' + item.ID + '</span>';
return itemElement;
}
});*/
}
attach(callback) {
this.callback = callback;
}
}

View File

@@ -36,7 +36,7 @@ import { Form } from "../Widgets/Form.js";
import pixelHistIconUrl from '../../../../assets/icons/pixel_histogram.svg';
import { RadioButton } from "../Widgets/Radio.js";
import waveOnIconUrl from '../../../../assets/icons/wave-on.svg';
import { TogglerActionButton } from "../Button/Toggler.js";
import { WidgetTogglerButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
export class HiPSSettingsBox extends Box {
@@ -53,9 +53,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Contrast', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({
layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.luminositySettingsContent]
});
const content = Layout.vertical([[self.selector, self.spectraBtn], self.luminositySettingsContent]);
self.update({content})
}
},
@@ -67,7 +65,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Opacity', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.opacitySettingsContent]});
const content = Layout.vertical([[self.selector, self.spectraBtn], self.opacitySettingsContent]);
self.update({content})
}
},
@@ -78,7 +76,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Colormap', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.colorSettingsContent]});
const content = Layout.vertical([[self.selector, self.spectraBtn], self.colorSettingsContent]);
self.update({content})
}
},
@@ -90,7 +88,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Cutouts', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.pixelSettingsContent]});
const content = Layout.vertical([[self.selector, self.spectraBtn], self.pixelSettingsContent]);
self.update({content})
}
},
@@ -211,47 +209,32 @@ import { TogglerActionButton } from "../Button/Toggler.js";
{
label: 'min cut:',
type: 'number',
cssStyle: {
width: '6rem',
},
tooltip: {content: 'Min cut', position: {direction: 'bottom'}},
name: 'mincut',
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])
}
},
cssStyle: { width: '7rem' }
},
{
type: 'number',
label: 'max cut:',
cssStyle: {
width: '6rem',
},
tooltip: {content: 'Max cut', position: {direction: 'bottom'}},
name: 'maxcut',
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)
}
},
cssStyle: { width: '7rem' }
}]
});
let colorSettingsContent = new Form({
subInputs: [{
label: 'colormap:',
label: 'cmap:',
type: 'select',
name: 'cmap',
value: 'native',
@@ -302,21 +285,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,10 +316,10 @@ 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({
self.spectraBtn = new WidgetTogglerButton({
content: 'Spectra',
icon: {
size: 'small',
@@ -343,16 +328,13 @@ import { TogglerActionButton } from "../Button/Toggler.js";
},
tooltip: {content: 'Show/hide spectra', position: {direction: 'bottom'}},
toggled: true,
actionOn: () => {
enable: (o) => {
spectraDisplayer.attachHiPS3D(options.layer)
spectraDisplayer.show()
},
actionOff: () => {
spectraDisplayer.hide()
}
widget: spectraDisplayer
});
self.update({content: Layout.vertical([Layout.horizontal([self.selector, self.spectraBtn]), self.opacitySettingsContent])})
self.update({content: Layout.vertical([[self.selector, self.spectraBtn], self.opacitySettingsContent])})
}
this._update(options.layer)
@@ -362,9 +344,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
}
_addListeners() {
let self = this;
ALEvent.HIPS_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, (e) => {
ALEvent.LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, (e) => {
const hips = e.detail.layer;
let selectedLayer = this.options.layer;

View File

@@ -36,6 +36,7 @@ import { Utils } from "../../Utils";
import { View } from "../../View.js";
import { HiPSSettingsBox } from "./HiPSSettingsBox.js";
import hipsIconUrl from "../../../../assets/icons/hips.svg";
import treeIconUrl from "../../../../assets/icons/tree.svg";
import showIconUrl from "../../../../assets/icons/show.svg";
import addIconUrl from "../../../../assets/icons/plus.svg";
import hideIconUrl from "../../../../assets/icons/hide.svg";
@@ -43,15 +44,19 @@ 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 { TogglerActionButton } from "../Button/Toggler.js";
import swapIcon from '../../../../assets/icons/swap.svg'
import { WidgetTogglerButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { Box } from "../Widgets/Box.js";
import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
import { Input } from "../Widgets/Input.js";
import { Image } from "../../Image.js";
import { HiPSBrowserBox } from "./HiPSBrowserBox.js";
import { HiPSCompositeBox } from "./HiPSCompositeBox.js"
import { Catalog } from "../../Catalog.js";
import { ProgressiveCat } from "../../ProgressiveCat.js";
import { Form } from "../Widgets/Form.js";
import { HiPSSelector } from "./../Input/HiPSSelector.js";
import { HiPS } from "../../HiPS.js";
export class OverlayStackBox extends Box {
/*static previewImagesUrl = {
@@ -75,7 +80,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 +103,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 +114,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",
@@ -121,7 +126,7 @@ export class OverlayStackBox extends Box {
},
};
// Constructor
constructor(aladin, stackBtn) {
constructor(aladin) {
super(
{
close: true,
@@ -133,18 +138,14 @@ export class OverlayStackBox extends Box {
},
aladin.aladinDiv
);
this.stackBtn = stackBtn;
this.cachedHiPS = {};
this.aladin = aladin;
this.mode = "stack";
this._addListeners();
this.mocHiPSUrls = {};
this.ui = {};
this.HiPSui = {};
let self = this;
// Add overlay button
this.addOverlayBtn = new CtxMenuActionButtonOpener(
@@ -154,6 +155,7 @@ export class OverlayStackBox extends Box {
size: "small",
monochrome: true,
},
openDirection: 'right',
tooltip: {
content: "A catalog, MOC or footprint",
position: { direction: "top" },
@@ -188,8 +190,6 @@ export class OverlayStackBox extends Box {
o.stopPropagation();
o.preventDefault();
//self._hide();
const simbadHiPS = A.catalogHiPS(
OverlayStackBox.predefinedCats.simbad
.url,
@@ -205,8 +205,6 @@ export class OverlayStackBox extends Box {
o.stopPropagation();
o.preventDefault();
//self._hide();
const simbadHiPS = A.catalogHiPS(
OverlayStackBox.predefinedCats.gaia.url,
OverlayStackBox.predefinedCats.gaia
@@ -488,6 +486,7 @@ export class OverlayStackBox extends Box {
size: "small",
monochrome: true,
},
openDirection: 'right',
ctxMenu: [
{
label: {
@@ -502,28 +501,12 @@ export class OverlayStackBox extends Box {
cursor: "help",
},
},
content: "Add new survey",
content: "Add a new HiPS",
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
/*self._hide();
self.hipsSelectorBox = new HiPSSelectorBox(self.aladin);
// attach a callback
self.hipsSelectorBox.attach(
(HiPSId) => {
let name = Utils.uuidv4()
self.aladin.setOverlayImageLayer(HiPSId, name)
self.show();
}
);
self.hipsSelectorBox._show({
position: self.position,
});*/
self.aladin.addNewImageLayer(
'P/DSS2/color'
);
@@ -532,7 +515,7 @@ export class OverlayStackBox extends Box {
{
label: {
icon: {
url: hipsIconUrl,
url: treeIconUrl,
monochrome: true,
tooltip: {
content: "From our database...",
@@ -548,10 +531,50 @@ export class OverlayStackBox extends Box {
e.stopPropagation();
e.preventDefault();
if (!self.hipsBrowser)
self.hipsBrowser = new HiPSBrowserBox(aladin);
if (!aladin.hipsBrowser)
aladin.hipsBrowser = new HiPSBrowserBox(aladin);
self.hipsBrowser._show({position: {
let newLayer = Utils.uuidv4();
aladin.hipsBrowser._show({
selected: (hips) => {
let oldHiPS = aladin.getOverlayImageLayer(newLayer);
if (oldHiPS && hips.id === oldHiPS.id) {
return;
}
aladin.setOverlayImageLayer(hips, newLayer);
},
position: {
anchor: 'center center'
}
});
},
},
{
label: {
icon: {
url: hipsIconUrl,
monochrome: true,
tooltip: {
content: "Combine different surveys into a color one!",
position: { direction: "right" },
},
cssStyle: {
cursor: "help",
},
},
content: "Add a composite HiPS",
},
disabled: true,
action: (e) => {
e.stopPropagation();
e.preventDefault();
if (!self.hipsCompositeBox)
self.hipsCompositeBox = new HiPSCompositeBox(aladin);
self.hipsCompositeBox._show({position: {
anchor: 'center center'
}});
},
@@ -583,8 +606,9 @@ export class OverlayStackBox extends Box {
ContextMenu.webkitDir({
label: "Load local HiPS",
action(files) {
let id = files[0].webkitRelativePath.split("/")[0];
let name = id;
// Give a different id at each loading.
let id = Utils.uuidv4();
let name = files[0].webkitRelativePath.split("/")[0];
let hips = self.aladin.createImageSurvey(
id,
@@ -647,36 +671,29 @@ export class OverlayStackBox extends Box {
}
);
ALEvent.GRAPHIC_OVERLAY_LAYER_CHANGED.listenedBy(
ALEvent.LAYER_ADDED.listenedBy(
this.aladin.aladinDiv,
function (e) {
updateOverlayList();
}
);
ALEvent.HIPS_LAYER_ADDED.listenedBy(
this.aladin.aladinDiv,
function (e) {
updateOverlayList();
}
);
ALEvent.HIPS_LAYER_SWAP.listenedBy(this.aladin.aladinDiv, function (e) {
ALEvent.LAYER_SWAPPED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_REMOVED.listenedBy(
ALEvent.LAYER_REMOVED.listenedBy(
this.aladin.aladinDiv,
function (e) {
updateOverlayList();
}
);
ALEvent.HIPS_LAYER_CHANGED.listenedBy(
ALEvent.LAYER_CHANGED.listenedBy(
this.aladin.aladinDiv,
function (e) {
const hips = e.detail.layer;
let ui = self.HiPSui[hips.layer];
let ui = self.ui[hips.layer];
if (!ui) {
return;
@@ -703,90 +720,65 @@ export class OverlayStackBox extends Box {
);
updateOverlayList();
// Add a listener for HiPS list changes
ALEvent.FAVORITE_HIPS_LIST_UPDATED.listenedBy(document.body, (event) => {
let favoritesHips = event.detail;
self.cachedHiPS = {};
for (var hips of favoritesHips) {
let key = hips.name || hips.id || hips.url;
self.cachedHiPS[key] = hips;
}
// Update the options of the selector
const favorites = Object.keys(self.cachedHiPS);
for (var key in self.HiPSui) {
let hips = self.HiPSui[key];
let currentHiPS = hips.HiPSSelector.options.value
let favoritesCopy = [...favorites];
// add the current hips to the selector as well, even if it has been manually
// removed from the HiPSList
if (favoritesCopy.indexOf(currentHiPS) < 0) {
favoritesCopy.push(currentHiPS)
}
// one must add the current HiPS too!
favoritesCopy.sort();
hips.HiPSSelector.update({value: currentHiPS, options: favoritesCopy});
}
});
}
_hide() {
for (var key in this.HiPSui) {
let hips = this.HiPSui[key];
if (hips.settingsBtn.toggled) {
for (var key in this.ui) {
let ui = this.ui[key];
if (ui.settingsBtn && ui.settingsBtn.toggled) {
// toggle off
hips.settingsBtn.toggle();
ui.settingsBtn.toggle();
}
}
/*if (this.hipsBrowser) {
this.hipsBrowser._hide();
}*/
if (this.addOverlayBtn) this.addOverlayBtn.close();
/*if (this.catBox) {
this.catBox._hide();
}*/
if (this.addOverlayBtn) this.addOverlayBtn.hideMenu();
if (this.addHiPSBtn) this.addHiPSBtn.hideMenu();
// toggle the button because the window is closed
this.stackBtn.update({toggled: false});
if (this.addHiPSBtn) this.addHiPSBtn.close();
super._hide();
}
createLayout() {
this.HiPSui = {};
delete() {
if (!this.ui) {
return
}
let layout = [Layout.horizontal([this.addOverlayBtn, "Overlays"])];
for (let component of Object.values(this.ui)) {
for (let elt of Object.values(component)) {
elt.remove && elt.remove()
}
}
}
createLayout() {
this.delete()
this.ui = {};
let layout = [[this.addOverlayBtn, "&nbsp;Overlays"]];
layout = layout.concat(this._createOverlaysList());
layout.push(
Layout.horizontal({
layout: [
this.addHiPSBtn,
"Surveys",
this.filterEnabler,
this.filterBtn,
],
})
[
this.addHiPSBtn,
"&nbsp;Surveys",
this.filterEnabler,
this.filterBtn,
],
);
layout = layout.concat(this._createSurveysList());
return Layout.vertical({ layout });
return Layout.vertical(layout,
{
cssStyle: {
overflowWrap: "anywhere",
wordBreak: "break-word",
}
}
);
}
_createOverlaysList() {
let self = this;
let aladin = self.aladin;
let layout = [];
const overlays = Array.from(this.aladin.getOverlays())
@@ -797,8 +789,7 @@ export class OverlayStackBox extends Box {
// list of overlays
for (const overlay of overlays) {
const name = overlay.name;
let optBtn = [];
optBtn.push(new ActionButton({
let showBtn = new ActionButton({
size: "small",
icon: {
url: overlay.isShowing ? showIconUrl : hideIconUrl,
@@ -823,25 +814,10 @@ export class OverlayStackBox extends Box {
});
}
},
}));
optBtn.push(new ActionButton({
icon: {
url: removeIconUrl,
monochrome: true,
},
size: "small",
/*cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},*/
tooltip: {
content: "Remove",
position: { direction: "top" },
},
action(e) {
self.aladin.removeLayer(overlay);
},
}));
});
let optBtn = [
showBtn,
];
if (overlay.serialize) {
optBtn.push(new ActionButton({
@@ -861,46 +837,108 @@ 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>",
Layout.horizontal({ layout: optBtn }),
],
cssStyle: {
textAlign: "center",
display: "flex",
alignItems: "center",
listStyle: "none",
justifyContent: "space-between",
width: "100%",
},
});
if (overlay instanceof Catalog || overlay instanceof ProgressiveCat) {
let catSettingsBox = new Box({
close: false,
content: new Form({
subInputs: [
{
label: 'Size',
tooltip: {content: 'Size of the sources', position: {direction: 'right'}},
name: 'size',
type: 'range',
min: 2.0,
max: 30.0,
value: overlay.sourceSize,
change: (e) => {
const size = +e.target.value;
overlay.setSourceSize(size)
}
},
{
label: 'Shape',
name: 'shape',
type: 'select',
options: [
{ value: "plus", label: "+" },
{ value: "rhomb", label: "◇" },
{ value: "triangle", label: "△" },
{ value: "cross", label: "✕" },
{ value: "square", label: "□" },
{ value: "circle", label: "○" },
],
value: (overlay.shapeFn && "square") || overlay.shape,
change: (e) => {
const shape = e.target.value
overlay.setShape(shape)
}
},
{
label: 'Color',
name: 'color',
type: 'color',
value: overlay.color,
change: (e) => {
let hex = e.target.value;
overlay.setColor(hex)
}
},
]
}),
}, this.aladin.aladinDiv);
catSettingsBox._hide()
/*if(!Utils.hasTouchScreen()) {
layout.push({
label: item,
cssStyle,
hover(e) {
showBtn.el.style.visibility = 'visible'
deleteBtn.el.style.visibility = 'visible'
// catalog settings
let catSettingsBtn = new WidgetTogglerButton({
icon: { url: settingsIconUrl, monochrome: true },
size: "small",
tooltip: {
content: "Settings",
position: { direction: "top" },
},
unhover(e) {
showBtn.el.style.visibility = 'hidden'
deleteBtn.el.style.visibility = 'hidden'
toggled: false,
enable: (_) => {
// toggle off the other settings if opened
for (var l in self.ui) {
let ui = self.ui[l]
if (l != name) {
if (ui.settingsBtn)
ui.settingsBtn.close();
}
}
let spectraDisplayer = aladin.view.spectraDisplayer;
if (spectraDisplayer)
spectraDisplayer.attachHiPS3D(options.layer)
},
})
} else {
layout.push({
label: item,
cssStyle
})
}*/
layout.push(item);
widget: catSettingsBox,
openDirection: "right"
});
optBtn.push(catSettingsBtn);
if (!(name in self.ui)) {
self.ui[name] = {
settingsBox: catSettingsBox,
settingsBtn: catSettingsBtn,
showBtn,
};
}
}
optBtn.push(ActionButton.BUTTONS(self.aladin).remove(
(e) => {
self.aladin.removeLayer(overlay);
}
));
layout.push([
this._addOverlayIcon(overlay),
'<div class="aladin-overlay-label">' + name + "</div>",
optBtn
]);
}
return layout;
@@ -909,83 +947,96 @@ export class OverlayStackBox extends Box {
_createSurveysList() {
let self = this;
const layers = Array.from(self.aladin.getStackLayers())
let aladin = self.aladin;
const layers = Array.from(aladin.getStackLayers())
.reverse()
.map((name) => {
let overlay = self.aladin.getOverlayImageLayer(name);
let overlay = aladin.getOverlayImageLayer(name);
return overlay;
});
// survey list
let layout = [];
let hipsOptions = Object.keys(self.cachedHiPS);
hipsOptions.sort()
for (const layer of layers) {
let options = Array.from([...hipsOptions])
let value = layer.name || layer.id
if (options.indexOf(value) < 0) {
options.push(value)
for (const hips of layers) {
if (!hips) {
continue;
}
let HiPSSelector = Input.select({
value,
options,
title: layer.name,
change: (e) => {
let HiPSselect = new HiPSSelector({
layer: hips,
change(e) {
let name = e.target.value;
// search for the
let overlayLayer;
if (name in self.cachedHiPS) {
// it is an hips
let HiPSOptions = self.cachedHiPS[name];
if (name === "More...") {
if (!aladin.hipsBrowser) {
aladin.hipsBrowser = new HiPSBrowserBox(aladin);
}
aladin.hipsBrowser._show({
selected: (hips) => {
self.aladin.setOverlayImageLayer(hips, hips.layer);
},
position: { anchor: "center center" }
});
return;
}
let overlayLayer;
if (name in HiPSSelector.cachedHiPS) {
// it is an hips
let HiPSOptions = HiPSSelector.cachedHiPS[name];
overlayLayer = A.HiPS(HiPSOptions.id || HiPSOptions.url, HiPSOptions);
} else {
overlayLayer = layer
overlayLayer = hips
}
self.aladin.setOverlayImageLayer(overlayLayer, layer.layer);
aladin.setOverlayImageLayer(overlayLayer, hips.layer);
}
});
let deleteBtn = ActionButton.createSmallSizedIconBtn({
icon: { url: removeIconUrl, monochrome: true },
disable: layer.layer === "base",
tooltip: { content: "Remove", position: { direction: "top" } },
action(e) {
self.aladin.removeImageLayer(layer.layer);
action: (e) => {
aladin.removeImageLayer(hips.layer);
// remove HiPS cube player if any
self.aladin.removeUIByName("cube_displayer" + layer.layer)
aladin.removeUIByName("cube_displayer" + hips.layer)
let spectraDisplayer = aladin.view.spectraDisplayer;
if (hips instanceof HiPS && spectraDisplayer && hips === spectraDisplayer.hips) {
spectraDisplayer._hide()
}
},
});
let prevOpacity = null;
let showBtn = ActionButton.createSmallSizedIconBtn({
icon: {
url: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
url: hips.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
monochrome: true,
},
tooltip: {
content: layer.getOpacity() === 0.0 ? "Show" : "Hide",
content: hips.getOpacity() === 0.0 ? "Show" : "Hide",
position: { direction: "top" },
},
action(e, btn) {
e.preventDefault();
e.stopPropagation();
let opacity = layer.getOpacity();
let opacity = hips.getOpacity();
if (opacity === 0.0) {
layer.setOpacity(1.0);
let newOpacity = prevOpacity || 1.0;
prevOpacity = null;
hips.setOpacity(newOpacity);
btn.update({
icon: { monochrome: true, url: showIconUrl },
tooltip: { content: "Hide" },
});
} else {
layer.setOpacity(0.0);
prevOpacity = opacity;
hips.setOpacity(0.0);
btn.update({
icon: { monochrome: true, url: hideIconUrl },
tooltip: { content: "Show" },
@@ -995,10 +1046,9 @@ export class OverlayStackBox extends Box {
});
let settingsBox = new HiPSSettingsBox(self.aladin);
settingsBox.update({ layer });
settingsBox._hide();
let settingsBtn = new TogglerActionButton({
let settingsBtn = new WidgetTogglerButton({
icon: { url: settingsIconUrl, monochrome: true },
size: "small",
tooltip: {
@@ -1006,121 +1056,74 @@ export class OverlayStackBox extends Box {
position: { direction: "top" },
},
toggled: false,
actionOn: (e) => {
enable: (_) => {
// toggle off the other settings if opened
for (var l in self.HiPSui) {
let ui = self.HiPSui[l]
for (var l in self.ui) {
let ui = self.ui[l]
if (l != layer.layer) {
if (l != hips.layer) {
ui.settingsBtn.close();
}
}
settingsBox._show({
position: {
nextTo: settingsBtn,
direction: "right",
aladin: self.aladin,
},
});
},
actionOff: (e) => {
settingsBox._hide();
settingsBox.update({ layer: hips });
},
widget: settingsBox,
openDirection: "right",
});
let loadMOCBtn = new ActionButton({
size: "small",
let loadMOCBtn = ActionButton.BUTTONS(self.aladin)
.addMOC({
name: hips.name,
url: hips.url + '/Moc.fits'
});
self.layer2swap = null;
let swapBtn = new ActionButton({
size: "small",
icon: {
url: Icon.dataURLFromSVG({ svg: Icon.SVG_ICONS.MOC }),
url: swapIcon,
size: "small",
monochrome: true,
},
tooltip: {
content: "Add coverage",
content: "Swap 2 layers",
position: { direction: "top" },
},
toggled: (() => {
let overlays = self.aladin.getOverlays();
let found = overlays.find(
(o) => o.type === "moc" && o.name === layer.name
);
return found !== undefined;
})(),
action: (e) => {
if (!loadMOCBtn.options.toggled) {
// load the moc
let moc = A.MOCFromURL(
layer.url + "/Moc.fits",
{ name: layer.name },
() => {
self.mocHiPSUrls[layer.url] = moc;
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message:
"Coverage of " +
layer.name +
" loaded",
duration: 2000,
type: "info",
});
}
loadMOCBtn.update({
toggled: true,
tooltip: {
content: "Remove coverage",
position: { direction: "top" },
},
});
}
);
self.aladin.addMOC(moc);
} else {
// unload the moc
let moc = self.mocHiPSUrls[layer.url];
self.aladin.removeLayer(moc);
delete self.mocHiPSUrls[layer.url];
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message:
"Coverage of " + layer.name + " removed",
duration: 2000,
type: "info",
});
toggled: false,
action: (_) => {
let toggled = swapBtn.options.toggled;
if (!toggled) {
if (!self.layer2swap) {
self.layer2swap = hips;
} else {
self.aladin.view.swapLayers(self.layer2swap.layer, hips.layer);
}
} else {
if (self.layer2swap) {
self.layer2swap = null;
}
loadMOCBtn.update({
toggled: false,
tooltip: {
content: "Add coverage",
position: { direction: "top" },
},
});
}
swapBtn.update({
toggled: !toggled,
});
},
});
let btns = [showBtn, settingsBtn];
if (!(layer instanceof Image)) {
if (!(hips instanceof Image)) {
btns.push(loadMOCBtn);
}
btns.push(deleteBtn);
let item = Layout.horizontal({
layout: [HiPSSelector, Layout.horizontal(btns)],
});
btns = btns.concat([swapBtn, deleteBtn]);
let item = Layout.horizontal([HiPSselect, Layout.horizontal(btns)]);
layout.push(item);
if (!(layer.layer in self.HiPSui)) {
self.HiPSui[layer.layer] = {
HiPSSelector,
if (!(hips.layer in self.ui)) {
self.ui[hips.layer] = {
HiPSSelector: HiPSselect,
settingsBox,
settingsBtn,
showBtn,
@@ -1186,7 +1189,5 @@ export class OverlayStackBox extends Box {
...options,
...{ position: this.position },
});
this.stackBtn.update({toggled: true});
}
}

View File

@@ -117,27 +117,12 @@ export class StatusBarBox extends Box {
this.el.title = task.message;
// create message div
let message = Layout.horizontal({
layout: task.message,
tooltip: {
content: task.message,
position: {
direction: "top",
},
hoverable: true,
delayShowUpTime: '500ms',
cssStyle: {
fontSize: 'x-small',
maxWidth: "200px",
"overflow-wrap": "break-word",
}
},
});
let message = Layout.horizontal(task.message);
message.addClass("aladin-status-bar-message")
this._show({
content: new Layout({layout: [StatusBarBox.icons[task.type], message], orientation: 'horizontal'}),
content: Layout.horizontal([StatusBarBox.icons[task.type], message]),
})
}

View File

@@ -49,7 +49,7 @@ export class ConeSearchActionButton extends ActionButton {
url: targetIconUrl
},
tooltip: options.tooltip,
disable: options.disable,
disabled: options.disabled,
cssStyle: {
backgroundPosition: 'center center',
cursor: 'pointer',

View File

@@ -28,10 +28,10 @@
*
*****************************************************************************/
import { ActionButton } from "../Widgets/ActionButton.js";
import { ContextMenu } from "../Widgets/ContextMenu.js";
import { WidgetTogglerButton } from "./Toggler.js";
/*
export class CtxMenuActionButtonOpener extends ActionButton {
// Constructor
constructor(options, aladin) {
let self;
@@ -48,35 +48,86 @@ export class CtxMenuActionButtonOpener extends ActionButton {
};
super({
...options,
cssStyle: {
backgroundPosition: 'center center',
cursor: 'pointer',
...options.cssStyle
},
action(e) {
enableTooltips()
let isHidden = self.ctxMenu.isHidden
let wasClosed = self.ctxMenu.isHidden;
self.close()
ContextMenu.hideAll();
if (self.ctxMenu.toggler === self && !wasClosed) {
return;
}
// If it was hidden then reopen it
if (isHidden) {
if (options.action) {
options.action(e)
}
self.open(e);
if (self.layout) {
self.ctxMenu.attach(self.layout)
}
// the panel is now open and we know the button has a tooltip
// => we close it!
if (self.tooltip && !self.ctxMenu.isHidden) {
self.tooltip.element().style.visibility = 'hidden'
self.tooltip.element().style.transitionDelay = '0ms';
self.ctxMenu.show({
position: {
nextTo: self,
direction: options.openDirection,
},
});
aladin.aladinDiv.addEventListener("click", enableTooltips)
}
},
...options,
})
self = this;
this.ctxMenu = aladin.contextMenu;
this.layout = options.ctxMenu;
}
close() {
this.closed = true;
this.ctxMenu._hide();
}
open(e) {
if (this.layout) {
this.ctxMenu.attach(this.layout, this)
}
this.ctxMenu.show({
position: {
nextTo: this,
direction: this.options.openDirection,
},
});
this.closed = false;
}
_hide() {
this.close();
super._hide();
}
}*/
export class CtxMenuActionButtonOpener extends WidgetTogglerButton {
// Constructor
constructor(options, aladin) {
let self;
const enableTooltips = () => {
aladin.aladinDiv.removeEventListener('click', enableTooltips);
aladin.aladinDiv.querySelectorAll('.aladin-tooltip')
// for each tooltips reset its visibility and transition delay
.forEach((t) => {
t.style.visibility = ''
t.style.transitionDelay = ''
})
};
super({
widget: aladin.contextMenu,
enable(e) {
enableTooltips()
// If it was hidden then reopen it
if (self.layout) {
self.ctxMenu.attach(self.layout, self)
}
// the panel is now open and we know the button has a tooltip
@@ -87,58 +138,22 @@ export class CtxMenuActionButtonOpener extends ActionButton {
aladin.aladinDiv.addEventListener("click", enableTooltips)
}
}
},
...options,
})
self = this;
let ctxMenu;
if (options.ctxMenu instanceof ContextMenu) {
ctxMenu = options.ctxMenu;
} else {
this.layout = options.ctxMenu;
ctxMenu = new ContextMenu(aladin, {hideOnClick: true, hideOnResize: true})
}
self.ctxMenu = ctxMenu;
}
hideMenu() {
this.ctxMenu._hide();
}
_hide() {
this.hideMenu();
super._hide();
this.ctxMenu = aladin.contextMenu;
this.layout = options.ctxMenu;
}
update(options) {
if(options.ctxMenu) {
if (options.ctxMenu instanceof ContextMenu) {
this.ctxMenu = options.ctxMenu
} else {
this.layout = options.ctxMenu;
}
}
if (!this.ctxMenu) {
this.ctxMenu = new ContextMenu(aladin, {hideOnClick: true, hideOnResize: true})
if (options && options.ctxMenu) {
this.layout = options.ctxMenu;
//this.ctxMenu.attach(this.layout, this)
}
super.update(options)
if (!this.ctxMenu.isHidden) {
if (this.layout) {
this.ctxMenu.attach(this.layout)
}
this.ctxMenu.show({
position: {
nextTo: this,
// it case it is not given then it will be computed by default
direction: options.openDirection,
},
});
}
}
}

Some files were not shown because too many files have changed in this diff Show More