Compare commits
52 Commits
customize-
...
composite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4781ffdef2 | ||
|
|
0a48b016f9 | ||
|
|
b699a4bc12 | ||
|
|
95dfcc2cff | ||
|
|
c3eca21aee | ||
|
|
4524586145 | ||
|
|
13c5dc1aa1 | ||
|
|
b84a444634 | ||
|
|
e16f34fdad | ||
|
|
c7fcf3d451 | ||
|
|
7960ef9bde | ||
|
|
c2e7838930 | ||
|
|
601928bc8c | ||
|
|
feb892fa03 | ||
|
|
60022b2659 | ||
|
|
c69aa990b6 | ||
|
|
42209b166e | ||
|
|
2ef5e47c27 | ||
|
|
337618d6ef | ||
|
|
832e8cd4bd | ||
|
|
28869645cd | ||
|
|
da8eb6f76e | ||
|
|
0e3a359108 | ||
|
|
af89535a91 | ||
|
|
8d244596ba | ||
|
|
2a23e83c13 | ||
|
|
7b8272795d | ||
|
|
4d8b4bfb21 | ||
|
|
ebf8845e83 | ||
|
|
f863ac902c | ||
|
|
75123e6bc8 | ||
|
|
d5d7d2a650 | ||
|
|
d22c25ea8a | ||
|
|
acef664b45 | ||
|
|
9e8db0379b | ||
|
|
032bb57517 | ||
|
|
f0fc39d2c8 | ||
|
|
2df32cb643 | ||
|
|
904d449006 | ||
|
|
2594aff1b6 | ||
|
|
547c5422d4 | ||
|
|
9bcc93877b | ||
|
|
3f6f247735 | ||
|
|
c6c7ad44c9 | ||
|
|
cdc1733c4f | ||
|
|
6e40dbbfc1 | ||
|
|
e03b16119b | ||
|
|
e3162426be | ||
|
|
5a285dabed | ||
|
|
2d04730623 | ||
|
|
390c9096d7 | ||
|
|
0e9998a7fc |
2
.github/workflows/npm-publish.yml
vendored
@@ -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
@@ -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
|
||||
|
||||
43
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
[](https://github.com/cds-astro/aladin-lite/actions/workflows/test.yml)
|
||||
[](https://cds-astro.github.io/aladin-lite)
|
||||
[](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
@@ -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
@@ -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 |
10
assets/icons/search-dark.svg
Normal 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 |
@@ -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 |
@@ -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 |
@@ -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
@@ -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
@@ -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 |
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
fov: 10,
|
||||
showContextMenu: true,
|
||||
fullScreen: true,
|
||||
showSimbadPointerControl: true,
|
||||
showShareControl: true,
|
||||
showSettingsControl: true,
|
||||
showStackLayerControl: true,
|
||||
samp: true,
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> <br><button id="stop">Stop</button>
|
||||
@@ -99,6 +105,14 @@
|
||||
<a class="pure-button nav-button nav-goto" href="#">Halley</a><br>
|
||||
→ <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>
|
||||
→ <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>
|
||||
→ <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
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'}),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
21
package.json
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -203,5 +203,3 @@ impl PixelType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUM_CHANNELS: usize = 6;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
])])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
);*/
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)?),
|
||||
};
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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>()));
|
||||
|
||||
30
src/js/A.js
@@ -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.
|
||||
|
||||
191
src/js/Aladin.js
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
})();
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -384,7 +384,6 @@ Utils.fetch = function(params) {
|
||||
// localhost url
|
||||
url = params.url;
|
||||
}
|
||||
|
||||
|
||||
let request = new Request(url, {
|
||||
method: params.method || 'GET',
|
||||
|
||||
352
src/js/View.js
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
267
src/js/gui/Box/HiPSCompositeBox.js
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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, " Overlays"]];
|
||||
|
||||
layout = layout.concat(this._createOverlaysList());
|
||||
|
||||
layout.push(
|
||||
Layout.horizontal({
|
||||
layout: [
|
||||
this.addHiPSBtn,
|
||||
"Surveys",
|
||||
this.filterEnabler,
|
||||
this.filterBtn,
|
||||
],
|
||||
})
|
||||
[
|
||||
this.addHiPSBtn,
|
||||
" 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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||