Compare commits
35 Commits
customize-
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
35
CHANGELOG.md
@@ -4,11 +4,40 @@
|
||||
|
||||
### What's Changed
|
||||
|
||||
* [perf] perform CPU computations with Vec3 and Matrix3 and not 4 dimensions matrices/vectors
|
||||
* [feat] lockNorthUp Aladin object new option locking the north pole up to the view center
|
||||
|
||||
## Released
|
||||
|
||||
### 3.8.0
|
||||
|
||||
* [fix] horizontal/vertical overlay lines appearing correctly <https://github.com/cds-astro/aladin-lite/issues/334>
|
||||
* [fix] layer opacity restored when switching from not visible to visible <https://github.com/cds-astro/aladin-lite/issues/332>
|
||||
* [feat] dark/light mode for the interface
|
||||
* [fix] polylines shapes size not consistent w.r.t to div size <https://github.com/cds-astro/aladin-lite/issues/331>
|
||||
|
||||
### 3.7.0-beta
|
||||
|
||||
#### What's Changed
|
||||
|
||||
* [feat] flip longitude axis global method on Aladin by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/245>
|
||||
* [feat] add rotation event by [@ManonMarchand][ManonMarchand] in <https://github.com/cds-astro/aladin-lite/pull/283>
|
||||
* [docs] just fixing typo in image's doc by [@ManonMarchand][ManonMarchand] in <https://github.com/cds-astro/aladin-lite/pull/284>
|
||||
* [docs] change to an image with correct astrometry in example by [@ManonMarchand][ManonMarchand] in <https://github.com/cds-astro/aladin-lite/pull/290>
|
||||
* [docs] clarify use of precision in Coo by [@alexgoff][alexgoff] in <https://github.com/cds-astro/aladin-lite/pull/294>
|
||||
* [feat] allow setting HiPS CORS and credential options by [@pmatsson][pmatsson] in <https://github.com/cds-astro/aladin-lite/pull/281>
|
||||
* [feat] color picker and read pixel(s) API methods by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/280>
|
||||
* [fix] chandra hips display and akari by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/306>
|
||||
* [enhancement] update to the new version of fitsrs by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/310>
|
||||
* [fix] 26 channel color offset by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/320>
|
||||
* [fix] Circle intersectsBBox by [@emellega][emellega] in <https://github.com/cds-astro/aladin-lite/pull/309>
|
||||
* [feat] anti aliasing on lines plotted by the GPU by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/239>
|
||||
* [feat] source custom color and size from its data content by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/321>
|
||||
* [feat] catalog new select method by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/322>
|
||||
* [feat] HiPS 3D impl by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/314>
|
||||
* [feat] customize share URL function by [@tboch][tboch] in <https://github.com/cds-astro/aladin-lite/pull/327>
|
||||
* enhancement: use TAP entry point to query NED by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/257>
|
||||
* [perf] refac geometrical computations using Vec3/Mat3 instead of Vec4/Mat4 [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/276>
|
||||
* [fix] distortion at poles by [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/247>
|
||||
* [feat] new aladin option `lockNorthUp` to keep north pole up [@bmatthieu3][bmatthieu3] in <https://github.com/cds-astro/aladin-lite/pull/272>
|
||||
|
||||
### 3.6.3
|
||||
|
||||
#### What's Changed
|
||||
|
||||
@@ -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
|
||||
|
||||
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 |
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.7.3-beta",
|
||||
"description": "An astronomical HiPS visualizer in the browser",
|
||||
"author": "Thomas Boch and Matthieu Baumann",
|
||||
"license": "GPL-3",
|
||||
@@ -30,13 +30,17 @@
|
||||
"HiPS"
|
||||
],
|
||||
"scripts": {
|
||||
"wasm": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
|
||||
"wasm:npm": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
|
||||
"wasm:prod": "wasm-pack build ./src/core --target web --release --out-name core -- --features \"webgl2 minify_shaders\" && wasm-opt -Oz --strip-debug --strip-producers --dce -o src/core/pkg/core_bg.wasm src/core/pkg/core_bg.wasm",
|
||||
"wasm:dev": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2",
|
||||
"wasm:dbg": "wasm-pack build --dev ./src/core --target web --out-name core -- --features=webgl2,dbg",
|
||||
"predeploy": "npm run build && rm -rf aladin-lite*.tgz && npm pack",
|
||||
"predeploy": "npm run build:prod && rm -rf aladin-lite*.tgz && npm pack",
|
||||
"deploy": "python3 deploy/deploy.py",
|
||||
"build": "npm run wasm && vite build",
|
||||
"build:npm": "npm run wasm:npm && vite build",
|
||||
"build:prod": "npm run wasm:prod && vite build",
|
||||
"build:dev": "npm run wasm:dev && vite build",
|
||||
"build:dbg": "npm run wasm:dbg && vite build",
|
||||
"dev": "npm run build && vite",
|
||||
"dev": "npm run build:dev && vite",
|
||||
"dev:dbg": "npm run build:dbg && vite",
|
||||
"serve": "npm run dev",
|
||||
"serve:dbg": "npm run dev:dbg",
|
||||
@@ -45,14 +49,15 @@
|
||||
"test:playwright": "npx playwright test",
|
||||
"test:update-snapshots": "npx playwright test --update-snapshots",
|
||||
"doc": "jsdoc -c jsdoc.json src/js src/js/shapes src/js/libs/astro && cp aladin-logo.png docs/ && cp jsdoc-custom-style.css docs/ && cp jsdoc-make-responsive.js docs/",
|
||||
"doc:dev": "npm run doc && open docs/index.html"
|
||||
"doc:dev": "npm run doc && open docs/index.html",
|
||||
"analyze": "vite build --mode analyze"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.0",
|
||||
"docdash": "^2.0.2",
|
||||
"jsdoc": "^4.0.2",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"vite": "^4.3.8",
|
||||
"vite-plugin-glsl": "^1.1.2",
|
||||
"vite-plugin-top-level-await": "^1.4.1",
|
||||
"vite-plugin-wasm": "^3.2.2",
|
||||
"vite-plugin-wasm-pack": "^0.1.12"
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "aladin-lite"
|
||||
description = "Aladin Lite v3 introduces a new graphical engine written in Rust with the use of WebGL"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/cds-astro/aladin-lite"
|
||||
version = "3.7.0"
|
||||
version = "3.7.3-beta"
|
||||
authors = [ "baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr",]
|
||||
edition = "2018"
|
||||
|
||||
@@ -18,21 +18,20 @@ futures = "0.3.12"
|
||||
js-sys = "0.3.47"
|
||||
wasm-bindgen-futures = "0.4.20"
|
||||
cgmath = "*"
|
||||
# url-lite = "0.1.0"
|
||||
serde_json = "1.0.104"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
enum_dispatch = "0.3.8"
|
||||
wasm-bindgen = "=0.2.92"
|
||||
#wasm-streams = "0.3.0"
|
||||
async-channel = "1.8.0"
|
||||
mapproj = "0.3.0"
|
||||
fitsrs = "0.3.4"
|
||||
colorgrad = "0.6.2"
|
||||
fitsrs = "0.4.1"
|
||||
|
||||
[features]
|
||||
webgl1 = [ "al-core/webgl1", "al-api/webgl1", "web-sys/WebGlRenderingContext", "web-sys/AngleInstancedArrays", "web-sys/ExtSRgb", "web-sys/OesTextureFloat",]
|
||||
webgl2 = [ "al-core/webgl2", "al-api/webgl2", "web-sys/WebGl2RenderingContext", "web-sys/WebGlVertexArrayObject", "web-sys/ExtColorBufferFloat",]
|
||||
dbg = [ "dep:console_error_panic_hook",]
|
||||
minify_shaders = []
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
@@ -84,7 +83,7 @@ overflow-checks = false
|
||||
lto = true
|
||||
panic = "abort"
|
||||
incremental = false
|
||||
codegen-units = 16
|
||||
codegen-units = 1
|
||||
rpath = false
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
|
||||
@@ -179,8 +179,9 @@ impl HiPSProperties {
|
||||
#[wasm_bindgen]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ImageExt {
|
||||
#[serde(alias = "fits", alias = "fits.fz")]
|
||||
Fits,
|
||||
#[serde(alias = "fits.fz")]
|
||||
FitsFz,
|
||||
Jpeg,
|
||||
Png,
|
||||
Webp,
|
||||
@@ -199,6 +200,7 @@ pub enum DataproductType {
|
||||
impl std::fmt::Display for ImageExt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
ImageExt::FitsFz => write!(f, "fits.fz"),
|
||||
ImageExt::Fits => write!(f, "fits"),
|
||||
ImageExt::Png => write!(f, "png"),
|
||||
ImageExt::Jpeg => write!(f, "jpg"),
|
||||
|
||||
@@ -7,9 +7,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
js-sys = "0.3.47"
|
||||
cgmath = "*"
|
||||
#jpeg-decoder = "0.3.0"
|
||||
#png = "0.17.6"
|
||||
fitsrs = "0.3.4"
|
||||
fitsrs = "0.4.1"
|
||||
al-api = { path = "../al-api" }
|
||||
serde = { version = "^1.0.59", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -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();
|
||||
@@ -1587,15 +1606,18 @@ impl App {
|
||||
}
|
||||
|
||||
let now = Time::now();
|
||||
let dragging_duration = (now - self.time_start_dragging).as_secs();
|
||||
let dragging_vel = self.dist_dragging / dragging_duration;
|
||||
let avg_vel = self.vel_history.iter().copied().sum::<f32>() / self.vel_history.len() as f32;
|
||||
|
||||
// Detect if there has been a recent acceleration
|
||||
// It is also possible that the dragging time is too short and if it is the case, trigger the inertia
|
||||
let recent_acceleration = (Time::now() - self.time_mouse_high_vel).as_secs() < 0.1
|
||||
|| (Time::now() - self.time_start_dragging).as_secs() < 0.1;
|
||||
// 2. Clamp minimum + maximum velocities
|
||||
let min_vel = 1000.0; // tweak
|
||||
|
||||
if dragging_vel < 2000.0 && !recent_acceleration {
|
||||
// 3. Better condition for “recent acceleration”
|
||||
let t_since_drag = (now - self.time_start_dragging).as_secs();
|
||||
let t_since_accel = (now - self.time_mouse_high_vel).as_secs();
|
||||
|
||||
let inertia_trigger =
|
||||
avg_vel > min_vel || ((t_since_drag < 0.15) || (t_since_accel < 0.15));
|
||||
if !inertia_trigger {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1605,10 +1627,8 @@ impl App {
|
||||
let center = self.camera.get_center();
|
||||
let axis = self.prev_cam_position.cross(*center).normalize();
|
||||
|
||||
//let delta_time = ((now - time_of_last_move).0 as f64).max(1.0);
|
||||
let delta_angle = math::vector::angle3(&self.prev_cam_position, center).to_radians();
|
||||
let ampl = delta_angle * (dragging_vel as f64) * 5e-3;
|
||||
//let ampl = (dragging_vel * 0.01) as f64;
|
||||
let ampl = (delta_angle * avg_vel as f64) * 5e-3;
|
||||
|
||||
self.inertia = Some(Inertia::new(ampl.to_radians(), axis, self.north_up))
|
||||
}
|
||||
@@ -1718,7 +1738,6 @@ impl App {
|
||||
}
|
||||
} else {
|
||||
/* 1. Rotate by computing the angle between the last and current position */
|
||||
|
||||
let d = math::vector::angle3(&prev_pos, &cur_pos);
|
||||
let axis = prev_pos.cross(cur_pos).normalize();
|
||||
|
||||
@@ -1728,9 +1747,39 @@ impl App {
|
||||
|
||||
self.prev_cam_position = prev_cam_position;
|
||||
self.request_for_new_tiles = true;
|
||||
|
||||
self._update_hips_location();
|
||||
}
|
||||
} else {
|
||||
self.out_of_fov = true;
|
||||
// approx move
|
||||
let origin2next = Vector2::new(s2x - s1x, s2y - s1y);
|
||||
|
||||
if origin2next != Vector2::zero() {
|
||||
let prev_pos = self.camera.get_center();
|
||||
|
||||
let prev_cam_position = self.get_center().vector();
|
||||
|
||||
let center_screen = self
|
||||
.projection
|
||||
.model_to_screen_space(&prev_cam_position, &self.camera)
|
||||
.unwrap();
|
||||
|
||||
let next_s = origin2next + center_screen;
|
||||
|
||||
if let Some(cur_pos) = self.projection.screen_to_model_space(&next_s, &self.camera)
|
||||
{
|
||||
let d = math::vector::angle3(prev_pos, &cur_pos);
|
||||
let axis = prev_pos.cross(cur_pos).normalize();
|
||||
|
||||
self.camera
|
||||
.apply_axis_rotation(&(-axis), d, &self.projection);
|
||||
|
||||
self.prev_cam_position = prev_cam_position;
|
||||
self.request_for_new_tiles = true;
|
||||
|
||||
self._update_hips_location();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1758,6 +1807,8 @@ impl App {
|
||||
pub(crate) fn set_zoom_factor(&mut self, zoom_factor: f64) {
|
||||
self.camera.set_zoom_factor(zoom_factor, &self.projection);
|
||||
|
||||
self._update_hips_location();
|
||||
|
||||
self.request_for_new_tiles = true;
|
||||
self.request_redraw = true;
|
||||
}
|
||||
|
||||
@@ -315,11 +315,7 @@ impl CameraViewPort {
|
||||
}
|
||||
|
||||
pub fn compute_ndc_to_clip_factor(&mut self, proj: &ProjectionType) {
|
||||
self.ndc_to_clip = if self.height < self.width {
|
||||
Vector2::new(1.0, (self.height as f64) / (self.width as f64))
|
||||
} else {
|
||||
Vector2::new((self.width as f64) / (self.height as f64), 1.0)
|
||||
};
|
||||
self.ndc_to_clip = Vector2::new(1.0, (self.height as f64) / (self.width as f64));
|
||||
|
||||
let bounds_size_ratio = proj.bounds_size_ratio();
|
||||
self.ndc_to_clip.y *= bounds_size_ratio;
|
||||
@@ -570,42 +566,48 @@ impl CameraViewPort {
|
||||
}
|
||||
|
||||
fn compute_texture_depth(&mut self) {
|
||||
/*// Compute a depth from a number of pixels on screen
|
||||
let width = self.width;
|
||||
let aperture = self.aperture.0 as f32;
|
||||
// Compute a depth from a number of pixels on screen
|
||||
/*let width = self.width;
|
||||
let aperture = self.aperture as f32;
|
||||
|
||||
let angle_per_pixel = aperture / width;
|
||||
let angle_per_pixel = aperture / width;
|
||||
|
||||
let two_power_two_times_depth_pixel =
|
||||
std::f32::consts::PI / (3.0 * angle_per_pixel * angle_per_pixel);
|
||||
let depth_pixel = (two_power_two_times_depth_pixel.log2() / 2.0).floor() as u32;
|
||||
let two_power_two_times_depth_pixel =
|
||||
std::f32::consts::PI / (3.0 * angle_per_pixel * angle_per_pixel);
|
||||
let depth_pixel = (two_power_two_times_depth_pixel.log2() / 2.0).ceil() as u32;
|
||||
|
||||
//let survey_max_depth = conf.get_max_depth();
|
||||
// The depth of the texture
|
||||
// A texture of 512x512 pixels will have a depth of 9
|
||||
const DEPTH_OFFSET_TEXTURE: u32 = 9;
|
||||
// The depth of the texture corresponds to the depth of a pixel
|
||||
// minus the offset depth of the texture
|
||||
self.texture_depth = if DEPTH_OFFSET_TEXTURE > depth_pixel {
|
||||
0_u8
|
||||
} else {
|
||||
(depth_pixel - DEPTH_OFFSET_TEXTURE) as u8
|
||||
};*/
|
||||
//let survey_max_depth = conf.get_max_depth();
|
||||
// The depth of the texture
|
||||
// A texture of 512x512 pixels will have a depth of 9
|
||||
const DEPTH_OFFSET_TEXTURE: u32 = 9;
|
||||
// The depth of the texture corresponds to the depth of a pixel
|
||||
// minus the offset depth of the texture
|
||||
self.texture_depth = if DEPTH_OFFSET_TEXTURE > depth_pixel {
|
||||
0_u8
|
||||
} else {
|
||||
(depth_pixel - DEPTH_OFFSET_TEXTURE) as u8
|
||||
};
|
||||
*/
|
||||
let w_screen_device_px = self.width as f64 / (self.dpi as f64);
|
||||
//let depth_pixel = 29_usize;
|
||||
|
||||
let w_screen_px = self.width as f64;
|
||||
let smallest_cell_size_px = self.dpi as f64;
|
||||
let mut depth_pixel = 29_usize;
|
||||
let pixel_angle_rad = self.get_aperture() / w_screen_device_px;
|
||||
|
||||
let hpx_cell_size_rad = (smallest_cell_size_px / w_screen_px) * self.get_aperture();
|
||||
|
||||
while depth_pixel > 0 {
|
||||
if crate::healpix::utils::MEAN_HPX_CELL_RES[depth_pixel] > hpx_cell_size_rad {
|
||||
break;
|
||||
// Find the smallest depth such that MEAN_HPX_CELL_RES[depth] > pixel_angle_rad
|
||||
let depth_pixel = match crate::healpix::utils::MEAN_HPX_CELL_RES.binary_search_by(|&res| {
|
||||
if res < pixel_angle_rad {
|
||||
std::cmp::Ordering::Greater
|
||||
} else if res > pixel_angle_rad {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
}) {
|
||||
Ok(idx) => idx, // exact match
|
||||
Err(idx) => idx,
|
||||
};
|
||||
|
||||
depth_pixel -= 1;
|
||||
}
|
||||
depth_pixel += 1;
|
||||
//al_core::log(&format!("{:?}", depth_pixel));
|
||||
const DEPTH_OFFSET_TEXTURE: usize = 9;
|
||||
self.texture_depth = if DEPTH_OFFSET_TEXTURE > depth_pixel {
|
||||
0_u8
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>()));
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
/* disable x swipe on chrome, firefox */
|
||||
/* see. https://stackoverflow.com/questions/30636930/disable-web-page-navigation-on-swipeback-and-forward */
|
||||
overscroll-behavior-x: none;
|
||||
overscroll-behavior-y: none; /* Prevents vertical pull-to-refresh */
|
||||
/* Hide the draggable boxes that goes out of the view */
|
||||
overflow: hidden;
|
||||
/* media query on the aladin lite container. not supported everywhere.
|
||||
@@ -18,9 +19,64 @@
|
||||
--aladin-color-border: #fff;
|
||||
}
|
||||
|
||||
.aladin-dark-theme {
|
||||
background-color: black;
|
||||
color: white;
|
||||
[data-theme="dark"] {
|
||||
--bg-color: black;
|
||||
--text-color: #ececec;
|
||||
--border-color: #ececec;
|
||||
--hover-color: greenyellow;
|
||||
--toggle-color: dodgerblue;
|
||||
--valid-color: greenyellow;
|
||||
--error-color: red;
|
||||
--border-size: 1px;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-color: #ececec;
|
||||
--text-color: #212121;
|
||||
--border-color: #212121;
|
||||
--hover-color: green;
|
||||
--toggle-color: dodgerblue;
|
||||
--border-size: 2px;
|
||||
--valid-color: greenyellow;
|
||||
--error-color: red;
|
||||
}
|
||||
|
||||
.aladin-tree {
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-tree .aladin-directory-path {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
width: 100%;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-tree ul {
|
||||
padding:0;
|
||||
margin:0;
|
||||
overflow-y: scroll;
|
||||
height: 300px;
|
||||
}
|
||||
.aladin-link {
|
||||
list-style-type: none;
|
||||
padding: 0.5em 0px;
|
||||
cursor:pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aladin-tree li:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.aladin-tree li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.aladin-tree li {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-lite-spectra-displayer .aladin-spectra-unit-selector {
|
||||
@@ -90,16 +146,18 @@
|
||||
.aladin-measurement-div {
|
||||
font-family: monospace;
|
||||
|
||||
display: block;
|
||||
border-top-left-radius: 0;
|
||||
|
||||
max-height: 30vh;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
border-radius: 3px;
|
||||
border-top-left-radius: 0;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
max-height: 200px;
|
||||
|
||||
overscroll-behavior: contain;
|
||||
scroll-behavior: auto;
|
||||
|
||||
}
|
||||
|
||||
.aladin-btn.tab {
|
||||
@@ -109,9 +167,11 @@
|
||||
}
|
||||
|
||||
.aladin-measurement-div table {
|
||||
border-collapse: collapse;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
white-space: nowrap;
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.aladin-measurement-div table::-webkit-scrollbar {
|
||||
@@ -119,26 +179,23 @@
|
||||
}
|
||||
|
||||
.aladin-measurement-div table thead {
|
||||
position: sticky;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
position: sticky; /* optional but helps */
|
||||
}
|
||||
|
||||
.aladin-measurement-div.aladin-dark-theme table thead {
|
||||
background-color: #000;
|
||||
color: white;
|
||||
|
||||
}
|
||||
|
||||
.aladin-measurement-div table th {
|
||||
.aladin-measurement-div table thead th {
|
||||
position: sticky;
|
||||
padding: 0.3em 0.5em;
|
||||
border-bottom: var(--border-size) solid var(--border-color);
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-measurement-div table tr td a {
|
||||
color: green;
|
||||
}
|
||||
.aladin-measurement-div table tr td a:hover {
|
||||
color: greenyellow;
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.aladin-measurement-div table tr td {
|
||||
@@ -231,25 +288,19 @@
|
||||
.aladin-box {
|
||||
padding: 0.2rem;
|
||||
background: whitesmoke;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
font-size: inherit;
|
||||
|
||||
font-family: monospace;
|
||||
background: #fff;
|
||||
|
||||
line-height: 1.3;
|
||||
color: #222;
|
||||
/*box-shadow: 0 0 6px rgba(0,0,0,0.2);*/
|
||||
|
||||
/* Allow scrolling but disable scroll bar */
|
||||
-ms-overflow-style: none; /* for Internet Explorer, Edge */
|
||||
scrollbar-width: none; /* for Firefox */
|
||||
/*overflow-y: auto;*/
|
||||
overflow-y: none;
|
||||
max-height: 500px;
|
||||
/*max-width: fit-content;*/
|
||||
height: fit-content;
|
||||
height: min-content;
|
||||
width: min-content;
|
||||
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-container canvas {
|
||||
@@ -281,7 +332,7 @@
|
||||
|
||||
.aladin-indicatorBtn {
|
||||
color: #605F61!important;
|
||||
border: 1px solid #AEAEAE!important;
|
||||
border: var(--border-size) solid #AEAEAE!important;
|
||||
|
||||
border-radius: 3px;
|
||||
font-size: 1rem;
|
||||
@@ -302,7 +353,7 @@
|
||||
margin-top: 0 0 2px 0;
|
||||
cursor:pointer;
|
||||
color: #605F61;
|
||||
border: 1px solid #AEAEAE;
|
||||
border: var(--border-size) solid #AEAEAE;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
font-size: 1.0rem;
|
||||
@@ -318,7 +369,7 @@
|
||||
.aladin-box-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #d2d2d2;
|
||||
margin: 5px 0px 5px -4px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.aladin-restore {
|
||||
@@ -348,19 +399,19 @@
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
border: 1px solid #484848;
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
color: black;
|
||||
background-color: #bababa;
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.aladin-btn.aladin-dark-theme.toggled {
|
||||
border-color: dodgerblue;
|
||||
.aladin-btn.toggled {
|
||||
border-color: var(--toggle-color);
|
||||
}
|
||||
.aladin-btn.aladin-dark-theme:hover, .aladin-input-select.aladin-dark-theme:hover {
|
||||
border-color: greenyellow;
|
||||
.aladin-btn:hover, .aladin-input-select:hover {
|
||||
border-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.aladin-btn.disabled {
|
||||
@@ -387,36 +438,39 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
.aladin-icon.aladin-dark-theme {
|
||||
background-color: transparent;
|
||||
}
|
||||
*/
|
||||
|
||||
.aladin-icon.aladin-dark-theme.aladin-icon-monochrome img {
|
||||
[data-theme="dark"] .aladin-icon.aladin-icon-monochrome img {
|
||||
filter: invert(100%) sepia(91%) saturate(0%) hue-rotate(169deg) brightness(115%) contrast(100%);
|
||||
}
|
||||
|
||||
.aladin-btn.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-color: white;
|
||||
.aladin-btn {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-measurement-div.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid white;
|
||||
.aladin-measurement-div {
|
||||
background-color: var(--bg-color);
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-measurement-div.aladin-dark-theme table {
|
||||
color: white;
|
||||
.aladin-measurement-div table {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-box.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
.aladin-box {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-input-select.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid white;
|
||||
.aladin-input-select {
|
||||
background-color: var(--bg-color);
|
||||
border: var(--border-size) solid var(--text-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-container .small-sized-icon {
|
||||
@@ -437,16 +491,33 @@
|
||||
height: 1.2rem;
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme:focus, .aladin-input-number.aladin-dark-theme:focus {
|
||||
border-color: dodgerblue;
|
||||
.aladin-input-text:focus, .aladin-input-number:focus {
|
||||
border-color: var(--toggle-color);
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme.search {
|
||||
text-shadow: 0px 0px 2px #000;
|
||||
[data-theme="light"] .aladin-input-text.search:focus {
|
||||
background-image: url(../../assets/icons/search.svg);
|
||||
background-size: 1.8rem;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 1.8rem;
|
||||
}
|
||||
|
||||
.aladin-input-text.search:focus, .aladin-input-text.search:hover {
|
||||
background-image: url(../../assets/icons/search-white.svg);
|
||||
[data-theme="light"] .aladin-input-text.search:hover {
|
||||
background-image: url(../../assets/icons/search.svg);
|
||||
background-size: 1.8rem;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 1.8rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .aladin-input-text.search:focus {
|
||||
background-image: url(../../assets/icons/search-dark.svg);
|
||||
background-size: 1.8rem;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 1.8rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .aladin-input-text.search:hover {
|
||||
background-image: url(../../assets/icons/search-dark.svg);
|
||||
background-size: 1.8rem;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 1.8rem;
|
||||
@@ -464,22 +535,12 @@
|
||||
box-shadow:inset 0px 0px 0px 1px #f00;
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme.aladin-not-valid {
|
||||
border: 1px solid red;
|
||||
.aladin-input-text.aladin-not-valid {
|
||||
border: var(--border-size) solid var(--error-color);
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme.aladin-valid {
|
||||
border: 1px solid yellowgreen;
|
||||
}
|
||||
|
||||
.aladin-cancelBtn {
|
||||
background-color: #ca4242;
|
||||
border-color: #bd3935;
|
||||
}
|
||||
|
||||
.aladin-validBtn {
|
||||
background-color: #42c739;
|
||||
border-color: #38a730;
|
||||
.aladin-input-text.aladin-valid {
|
||||
border: var(--border-size) solid var(--valid-color);
|
||||
}
|
||||
|
||||
.aladin-box-content > div {
|
||||
@@ -508,11 +569,11 @@
|
||||
}
|
||||
|
||||
.aladin-vertical-list > *:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.aladin-vertical-list > * {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.aladin-horizontal-list {
|
||||
@@ -576,7 +637,7 @@
|
||||
.aladin-popup {
|
||||
/*font-family: Verdana, Geneva, Tahoma, sans-serif;*/
|
||||
background: white;
|
||||
border: 1px solid #bbb;
|
||||
border: var(--border-size) solid #bbb;
|
||||
border-radius: 2px;
|
||||
padding: 4px;
|
||||
top: 80px;
|
||||
@@ -713,12 +774,8 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
/*font-family: Verdana, Geneva, Tahoma, sans-serif;*/
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
border-bottom: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-bottom: var(--border-size) solid var(--border-color);
|
||||
border-right: var(--border-size) solid var(--border-color);
|
||||
}
|
||||
|
||||
.aladin-context-menu .aladin-context-menu-item {
|
||||
@@ -731,29 +788,22 @@
|
||||
padding: 0.2rem 0.4rem;
|
||||
position: relative;
|
||||
|
||||
box-shadow:inset 1px 1px 0px 0px #fff;
|
||||
box-shadow:inset var(--border-size) var(--border-size) 0px 0px var(--border-color);
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
background-color: var(--bg-color);
|
||||
|
||||
margin: 0;
|
||||
box-sizing: content-box;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.aladin-context-menu .aladin-context-menu-item:first-of-type {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.aladin-context-menu .aladin-context-menu-item:last-of-type {
|
||||
border-bottom: 0;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
.aladin-context-menu .aladin-context-menu-item.aladin-context-menu-item-disabled {
|
||||
cursor: not-allowed;
|
||||
color: red;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.aladin-context-menu .aladin-context-menu-item.aladin-context-menu-item-selected::after {
|
||||
@@ -766,11 +816,11 @@
|
||||
}
|
||||
|
||||
.aladin-context-menu-item:hover {
|
||||
color: greenyellow;
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.aladin-context-menu-item:not(hover) {
|
||||
color: white;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-context-menu-item .aladin-context-sub-menu {
|
||||
@@ -784,15 +834,6 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/*.aladin-context-menu-item:first-of-type {
|
||||
border-top: 1px solid #d2d2d2;
|
||||
margin-top: -1px;
|
||||
}*/
|
||||
/*
|
||||
.aladin-context-menu .aladin-context-menu-item:hover > .aladin-context-sub-menu {
|
||||
display: block;
|
||||
}
|
||||
*/
|
||||
.aladin-context-menu .aladin-context-sub-menu.left {
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
@@ -847,9 +888,10 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme, .aladin-input-number.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid white;
|
||||
.aladin-input-text, .aladin-input-number {
|
||||
background-color: var(--bg-color);
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
color: var(--text-color)
|
||||
}
|
||||
|
||||
.aladin-input-text::placeholder {
|
||||
@@ -860,7 +902,7 @@
|
||||
/* Input select */
|
||||
.aladin-input-select {
|
||||
/* Reset */
|
||||
border: 1px solid white;
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
outline: 0;
|
||||
/*font: inherit;*/
|
||||
/* Personalize */
|
||||
@@ -943,58 +985,20 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
|
||||
|
||||
.aladin-input-range::-webkit-slider-container {
|
||||
background: white;
|
||||
background: var(--text-color);
|
||||
height: 0.1rem;
|
||||
min-height: 0.1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
.aladin-input-range-datalist {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
display: none;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding:0;
|
||||
margin:0;
|
||||
height: 0.1rem;
|
||||
top: 0rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.aladin-input-range-datalist option {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
position: absolute;
|
||||
transform: translate(-50%, 0);
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 0.1rem;
|
||||
border-radius: 0.1rem;
|
||||
height: 0.1rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #D3D3D3;
|
||||
}*/
|
||||
|
||||
.aladin-dark-theme {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* *********************************************** */
|
||||
|
||||
/* Tooltip */
|
||||
|
||||
.aladin-tooltip-container {
|
||||
position: relative;
|
||||
/* take the size of its inner div child */
|
||||
|
||||
font-family: monospace;
|
||||
line-height: 1rem;
|
||||
|
||||
/*z-index: 100;*/
|
||||
|
||||
float: left;
|
||||
}
|
||||
@@ -1016,6 +1020,9 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
|
||||
/* Position the tooltip text - see examples below! */
|
||||
position: absolute;
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
|
||||
/*font-family: Verdana, Geneva, Tahoma, sans-serif;*/
|
||||
|
||||
@@ -1117,6 +1124,15 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
left: 0.2rem;
|
||||
}
|
||||
|
||||
.aladin-link {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-link:hover {
|
||||
color: var(--hover-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.aladin-simbadPointer-control {
|
||||
position: absolute;
|
||||
top: 7.8rem;
|
||||
@@ -1143,17 +1159,21 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
|
||||
font-family: monospace;
|
||||
|
||||
color: white;
|
||||
color: var(--text-color);
|
||||
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.aladin-stack-box {
|
||||
width: 17rem;
|
||||
min-width: 17rem;
|
||||
}
|
||||
.aladin-item-selected {
|
||||
border: var(--border-size) solid orange;
|
||||
}
|
||||
|
||||
.aladin-HiPS-filter-box {
|
||||
margin-top: 0.4rem;
|
||||
/*width: 250px;*/
|
||||
}
|
||||
|
||||
.aladin-HiPS-filter-box .aladin-horizontal-list {
|
||||
@@ -1180,7 +1200,7 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
left: 4.75rem;
|
||||
font-family: monospace;
|
||||
|
||||
color: white;
|
||||
color: var(--text-color);
|
||||
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
@@ -1201,6 +1221,18 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.aladin-fov {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
[data-theme="light"] .aladin-fov {
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .aladin-fov {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-fov {
|
||||
position: absolute;
|
||||
bottom: 0.2rem;
|
||||
@@ -1232,13 +1264,9 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.aladin-status-bar.aladin-dark-theme {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.aladin-fov.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
.aladin-status-bar{
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.aladin-table {
|
||||
@@ -1248,8 +1276,8 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
max-width: calc(100% - 0.4rem);
|
||||
line-height: 1rem;
|
||||
}
|
||||
.aladin-measurement-div.aladin-dark-theme {
|
||||
color: white;
|
||||
.aladin-measurement-div {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.aladin-share-control {
|
||||
@@ -1310,22 +1338,4 @@ otherwise it fits its content options. If those are too big the select can go ou
|
||||
.aladin-cooFrame {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*@media screen and (max-width: 31rem) {
|
||||
.aladin-projection-control {
|
||||
display: none;
|
||||
}
|
||||
}*/
|
||||
/*@container (max-width: 40rem) {
|
||||
.aladin-input-text.aladin-dark-theme.search {
|
||||
width: 6rem;
|
||||
text-shadow: 0px 0px 2px #000;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 31rem) {
|
||||
.aladin-fov .aladin-monospace-text {
|
||||
display: none;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
25
src/js/A.js
@@ -97,6 +97,29 @@ A.aladin = function (divSelector, options) {
|
||||
divElement = divSelector;
|
||||
}
|
||||
|
||||
let retrieveDefaultMode = () => {
|
||||
const storedPreference = localStorage.getItem("theme");
|
||||
const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const theme = storedPreference || (systemPrefersDark ? "dark" : "light");
|
||||
return theme;
|
||||
}
|
||||
|
||||
let theme;
|
||||
if (options.mode) {
|
||||
let mode = options.mode.toLowerCase();
|
||||
|
||||
if (mode === 'dark' || mode === 'light') {
|
||||
theme = mode;
|
||||
} else {
|
||||
console.warn("Interface mode option not recognized, only `dark` or `light` are possible values.")
|
||||
theme = retrieveDefaultMode()
|
||||
}
|
||||
} else {
|
||||
theme = retrieveDefaultMode()
|
||||
}
|
||||
// Retrieve the data-theme from localStorage or system preferences
|
||||
divElement.setAttribute("data-theme", theme);
|
||||
|
||||
// Associate the CSS inside the div
|
||||
var cssStyleSheet = document.createElement('style')
|
||||
cssStyleSheet.classList.add("aladin-css");
|
||||
@@ -362,7 +385,7 @@ A.graphicOverlay = function (options) {
|
||||
* @returns {ProgressiveCat} Returns a new Overlay object representing the graphic overlay.
|
||||
*
|
||||
* @example
|
||||
* let gaia = A.catalogHiPS('http://axel.u-strasbg.fr/HiPSCatService/I/345/gaia2', {onClick: 'showTable', color: 'orange', name: 'Gaia', filter: myFilterFunction});
|
||||
* let gaia = A.catalogHiPS('http://axel.cds.unistra.fr/HiPSCatService/I/345/gaia2', {onClick: 'showTable', color: 'orange', name: 'Gaia', filter: myFilterFunction});
|
||||
* aladin.addCatalog(gaia)
|
||||
*/
|
||||
A.catalogHiPS = function (url, options) {
|
||||
|
||||
@@ -152,6 +152,7 @@ import { Polyline } from "./shapes/Polyline";
|
||||
* @property {boolean} [realFullscreen=false] - Whether to use real fullscreen mode.
|
||||
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
|
||||
* @property {boolean} [manualSelection=false] - When set to true, no selection will be performed, only events will be generated.
|
||||
* @property {string} [mode] - Interface theme, can be either 'dark' or 'light'. If not set, the mode will be retrieved from your browser preference or your localStorage.
|
||||
* @property {Object} [selector] - More options for the the selector.
|
||||
* @property {string} [selector.color] - Color of the selector, defaults to the color of the reticle. Can be a hex color or a function returning a hex color.
|
||||
* @property {number} [selector.lineWidth=2] - Width of the selector line.
|
||||
@@ -749,7 +750,7 @@ export let Aladin = (function () {
|
||||
longitudeReversed: false,
|
||||
realFullscreen: false,
|
||||
pixelateCanvas: true,
|
||||
manualSelection: false
|
||||
manualSelection: false,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -941,7 +942,7 @@ export let Aladin = (function () {
|
||||
objectName +
|
||||
"'";
|
||||
var url =
|
||||
"//simbad.u-strasbg.fr/simbad/sim-tap/sync?query=" +
|
||||
"//simbad.cds.unistra.fr/simbad/sim-tap/sync?query=" +
|
||||
encodeURIComponent(query) +
|
||||
"&request=doQuery&lang=adql&format=json&phase=run";
|
||||
|
||||
@@ -2004,7 +2005,7 @@ export let Aladin = (function () {
|
||||
* </ul>
|
||||
*/
|
||||
Aladin.prototype.setBaseImageLayer = function (urlOrHiPSOrFITS) {
|
||||
return this.setOverlayImageLayer(urlOrHiPSOrFITS, "base");
|
||||
return this.setOverlayImageLayer(urlOrHiPSOrFITS, (this.view.overlayLayers && this.view.overlayLayers[0]) || Utils.uuidv4());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2014,7 +2015,7 @@ export let Aladin = (function () {
|
||||
* @returns {HiPS|Image} - Returns the image layer corresponding to the base layer
|
||||
*/
|
||||
Aladin.prototype.getBaseImageLayer = function () {
|
||||
return this.view.getImageLayer("base");
|
||||
return this.view.getImageLayer(this.view.overlayLayers && this.view.overlayLayers[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -3003,7 +3004,7 @@ aladin.customizeShareURLFunction(() => {return 'https://sky.esa.int/esasky/?targ
|
||||
* @param {Function} [successCallback=<center the view on the FITS file>] - The callback function to be executed on a successful display.
|
||||
* The callback gives the ra, dec, and fov of the image; By default, it centers the view on the FITS file loaded.
|
||||
* @param {Function} [errorCallback] - The callback function to be executed if an error occurs during display.
|
||||
* @param {string} [layer="base"] - The name of the layer. If not specified, it will be replace the base layer.
|
||||
* @param {string} [layer] - The name of the layer. If not specified, it will be replace the base layer.
|
||||
*
|
||||
* @example
|
||||
aladin.displayFITS(
|
||||
@@ -3027,7 +3028,7 @@ aladin.displayFITS(
|
||||
options,
|
||||
successCallback,
|
||||
errorCallback,
|
||||
layer = "base"
|
||||
layer
|
||||
) {
|
||||
successCallback =
|
||||
successCallback ||
|
||||
@@ -3041,6 +3042,7 @@ aladin.displayFITS(
|
||||
successCallback,
|
||||
errorCallback
|
||||
);
|
||||
layer = layer || (this.view.overlayLayers && this.view.overlayLayers[0])
|
||||
return this.setOverlayImageLayer(image, layer);
|
||||
};
|
||||
|
||||
@@ -3117,7 +3119,6 @@ aladin.displayFITS(
|
||||
|
||||
get("https://alasky.unistra.fr/cgi/fits2HiPS", data).then(
|
||||
async (response) => {
|
||||
console.log(response, data)
|
||||
if (response.status != "success") {
|
||||
console.error("An error occured: " + response.message);
|
||||
if (errorCallback) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,6 @@ import { HiPSProperties } from "./HiPSProperties.js";
|
||||
import { Aladin } from "./Aladin.js";
|
||||
import { CooFrameEnum } from "./CooFrameEnum.js";
|
||||
import { Utils } from "./Utils"
|
||||
import { SpectraDisplayer } from "./SpectraDisplayer.js";
|
||||
|
||||
let PropertyParser = {};
|
||||
// Utilitary functions for parsing the properties and giving default values
|
||||
@@ -180,13 +179,17 @@ PropertyParser.isPlanetaryBody = function (properties) {
|
||||
* @property {number} [brightness=0.0] - The brightness value for the color configuration.
|
||||
* @property {number} [contrast=0.0] - The contrast value for the color configuration.
|
||||
* @property {string} [requestMode='cors'] - Determines how the request will interact with cross-origin resources.
|
||||
* - 'cors' - allow cross-origin requests with proper CORS headers.
|
||||
* - 'no-cors' - send the request without CORS.
|
||||
* - 'same-origin' - only allow requests to the same origin.
|
||||
* @property {string} [requestCredentials='omit'] - Specifies whether to send cookies and HTTP credentials with the request.
|
||||
* - 'omit' - never send credentials.
|
||||
* - 'same-origin' - send only for same-origin requests.
|
||||
* - 'include' - always send, even for cross-origin requests.
|
||||
* <ul>
|
||||
* <li>'cors' - allow cross-origin requests with proper CORS headers.</li>
|
||||
* <li>'no-cors' - send the request without CORS.</li>
|
||||
* <li>'same-origin' - only allow requests to the same origin.</li>
|
||||
* </ul>
|
||||
* @property {string} [requestCredentials='same-origin'] - Specifies whether to send cookies and HTTP credentials with the request.
|
||||
* <ul>
|
||||
* <li>'omit' - never send credentials.</li>
|
||||
* <li>'same-origin' - send only for same-origin requests.</li>
|
||||
* <li>'include' - always send, even for cross-origin requests.</li>
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -270,7 +273,7 @@ export let HiPS = (function () {
|
||||
this.name = (options && options.name) || id;
|
||||
this.startUrl = options.startUrl;
|
||||
this.requestMode = options && options.requestMode || 'cors';
|
||||
this.requestCredentials = options && options.requestCredentials || 'omit';
|
||||
this.requestCredentials = options && options.requestCredentials || 'same-origin';
|
||||
|
||||
this.slice = 0;
|
||||
|
||||
@@ -729,9 +732,16 @@ export let HiPS = (function () {
|
||||
*
|
||||
* @param {number} minCut - The low cut value to set for the HiPS.
|
||||
* @param {number} maxCut - The high cut value to set for the HiPS.
|
||||
* @param {string} [imgFormat] - The image format for which one wants to set the cuts. By default, the format used is the current imageFormat
|
||||
*/
|
||||
HiPS.prototype.setCuts = function (minCut, maxCut) {
|
||||
this.setOptions({minCut, maxCut})
|
||||
HiPS.prototype.setCuts = function (minCut, maxCut, imgFormat) {
|
||||
imgFormat = imgFormat?.toLowerCase();
|
||||
|
||||
if (imgFormat === "jpg") {
|
||||
imgFormat = "jpeg";
|
||||
}
|
||||
|
||||
this.setOptions({minCut, maxCut, cutFormat: imgFormat})
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -908,7 +918,7 @@ export let HiPS = (function () {
|
||||
imgFormat = "jpeg";
|
||||
}
|
||||
|
||||
if (!["fits", "png", "jpeg", "webp"].includes(imgFormat)) {
|
||||
if (!["fits", "png", "jpeg", "webp", "fits.fz"].includes(imgFormat)) {
|
||||
console.warn('Formats must lie in ["fits", "png", "jpg", "webp"]. imgFormat option property ignored');
|
||||
} else {
|
||||
// Passed the check, we erase the image format with the new one
|
||||
@@ -922,7 +932,7 @@ export let HiPS = (function () {
|
||||
this.imgFormat = imgFormat;
|
||||
|
||||
let [minCut, maxCut] = this.getCuts();
|
||||
if (minCut === undefined && maxCut === undefined && imgFormat === "fits") {
|
||||
if (minCut === undefined && maxCut === undefined && (imgFormat === "fits" || imgFormat === "fits.fz")) {
|
||||
// sets the default cuts parsed from the properties
|
||||
this.setCuts(this.defaultFitsMinCut, this.defaultFitsMaxCut)
|
||||
}
|
||||
|
||||
@@ -402,6 +402,7 @@ export let Image = (function () {
|
||||
|
||||
// Set the automatic computed cuts
|
||||
let [minCut, maxCut] = self.getCuts();
|
||||
|
||||
minCut = minCut || imageParams.min_cut;
|
||||
maxCut = maxCut || imageParams.max_cut;
|
||||
self.setCuts(
|
||||
|
||||
@@ -77,7 +77,7 @@ export let MeasurementTable = (function() {
|
||||
tooltip: {
|
||||
global: true,
|
||||
aladin: this.aladin,
|
||||
content: 'Press Shift + mouse wheel for scrolling horizontally'
|
||||
content: 'Press shift + mouse wheel for scrolling'
|
||||
},
|
||||
aladin: this.aladin,
|
||||
layout,
|
||||
|
||||
@@ -51,7 +51,7 @@ export class MocServer {
|
||||
//expr: "dataproduct_type=image",
|
||||
get: "record",
|
||||
fmt: "json",
|
||||
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
|
||||
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,client_category",
|
||||
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
|
||||
};
|
||||
|
||||
|
||||
@@ -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,7 +21,6 @@ import { ActionButton } from "./gui/Widgets/ActionButton";
|
||||
import { Input } from "./gui/Widgets/Input";
|
||||
import HomeIconUrl from '../../assets/icons/maximize.svg';
|
||||
import SpectraIconUrl from '../../assets/icons/freq.svg';
|
||||
import { ALEvent } from "./events/ALEvent";
|
||||
import { Utils } from "./Utils";
|
||||
import { Aladin } from "./Aladin";
|
||||
|
||||
@@ -142,6 +141,7 @@ export class SpectraDisplayer {
|
||||
this.minY = undefined;
|
||||
this.maxY = undefined;
|
||||
this.mouseFreq = undefined;
|
||||
this.enabled = true;
|
||||
|
||||
// One canvas for the spectra
|
||||
this.canvas = createPlotCanvas("spectra-line");
|
||||
@@ -169,7 +169,7 @@ export class SpectraDisplayer {
|
||||
SpectraDisplayer.UNIT.VELOCITY.label,
|
||||
],
|
||||
tooltip: {
|
||||
content: "Unit between frequency, wavelength and velocity",
|
||||
content: `Unit: ${SpectraDisplayer.UNIT.FREQUENCY.label}, ${SpectraDisplayer.UNIT.WAVELENGTH.label} and ${SpectraDisplayer.UNIT.VELOCITY.label}`,
|
||||
position: {direction: "right"}
|
||||
},
|
||||
change: (e) => {
|
||||
@@ -246,7 +246,7 @@ export class SpectraDisplayer {
|
||||
}
|
||||
|
||||
defineEventListeners() {
|
||||
let lastMouse = { x: 0, y: 0 };
|
||||
this.lastMouse = { x: 0, y: 0 };
|
||||
this.isDragging = false;
|
||||
|
||||
let canvas = this.canvas;
|
||||
@@ -257,32 +257,39 @@ export class SpectraDisplayer {
|
||||
let lastClickTime = 0;
|
||||
const DOUBLE_CLICK_DELAY = 300; // most operating systems uses duration between 250ms and 500ms by default.
|
||||
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mx = e.clientX - rect.left;
|
||||
const my = e.clientY - rect.top;
|
||||
|
||||
let mouseDownTime = 0;
|
||||
let mouseDownPos = { x: 0, y: 0 };
|
||||
const CLICK_TIME_THRESHOLD = 250; // ms
|
||||
const CLICK_MOVE_THRESHOLD = 5; // pixels
|
||||
|
||||
|
||||
Utils.on(canvas, 'mousedown touchstart', (e) => {
|
||||
mouseDownTime = Date.now();
|
||||
mouseDownPos = Utils.relMouseCoords(e);
|
||||
|
||||
const mx = mouseDownPos.x;
|
||||
const my = mouseDownPos.y;
|
||||
let v = this.data.values[Math.round(mx / this.scaleX)]
|
||||
|
||||
let len = this.data.values.length;
|
||||
|
||||
v = this.height - (v - this.minY) * this.scaleY
|
||||
if (my >= v) {
|
||||
this.isDragging = true;
|
||||
lastMouse = { x: mx, y: my };
|
||||
this.lastMouse = { x: mx, y: my };
|
||||
canvas.style.cursor = 'grabbing';
|
||||
} else {
|
||||
// check if the click is next to the center bar
|
||||
// Draw the vertical line that can be grabed to move the slice
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.lineWidth = 30;
|
||||
|
||||
this.ctx.moveTo(this.scaleX * len / 2, this.height);
|
||||
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
|
||||
this.ctx.strokeStyle = Aladin.DEFAULT_OPTIONS.reticleColor;
|
||||
this.ctx.lineWidth = 10;
|
||||
|
||||
if (this.ctx.isPointInStroke(mx, my)) {
|
||||
this.isDragging = true;
|
||||
lastMouse = { x: mx, y: my };
|
||||
this.lastMouse = { x: mx, y: my };
|
||||
canvas.style.cursor = 'grabbing';
|
||||
} else {
|
||||
// propagate event to its sibling
|
||||
@@ -298,9 +305,17 @@ export class SpectraDisplayer {
|
||||
altKey: e.altKey,
|
||||
metaKey: e.metaKey,
|
||||
button: e.button,
|
||||
changedTouches: e.changedTouches,
|
||||
targetTouches: e.targetTouches,
|
||||
relatedTarget: e.relatedTarget,
|
||||
};
|
||||
const event = new MouseEvent('mousedown', paramsEvent);
|
||||
let event;
|
||||
if (e.type === "mousedown") {
|
||||
event = new MouseEvent("mousedown", paramsEvent);
|
||||
} else {
|
||||
this.disableInteraction();
|
||||
event = new TouchEvent("touchstart", paramsEvent)
|
||||
}
|
||||
// Track timing to simulate dblclick
|
||||
const now = Date.now();
|
||||
if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
|
||||
@@ -322,16 +337,43 @@ export class SpectraDisplayer {
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mx = e.clientX - rect.left;
|
||||
const my = e.clientY - rect.top;
|
||||
Utils.on(canvas, 'mousemove touchmove', (e) => {
|
||||
if (!this.enabled) {
|
||||
let paramsEvent = {
|
||||
bubbles: e.bubbles,
|
||||
cancelable: e.cancelable,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
screenX: e.screenX,
|
||||
screenY: e.screenY,
|
||||
ctrlKey: e.ctrlKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
metaKey: e.metaKey,
|
||||
button: e.button,
|
||||
changedTouches: e.changedTouches,
|
||||
targetTouches: e.targetTouches,
|
||||
relatedTarget: e.relatedTarget,
|
||||
};
|
||||
|
||||
let touchEvent = new TouchEvent("touchmove", paramsEvent);
|
||||
this.view.catalogCanvas.dispatchEvent(touchEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
let mouseXY = Utils.relMouseCoords(e)
|
||||
const mx = mouseXY.x;
|
||||
const my = mouseXY.y;
|
||||
|
||||
// can be in the spectral area
|
||||
let v = this.data.values[Math.round(mx / this.scaleX)]
|
||||
let len = this.data.values.length;
|
||||
|
||||
v = this.height - (v - this.minY) * this.scaleY
|
||||
if (!this.isDragging) {
|
||||
this.isDragging = canvas.style.cursor === 'grabbing';
|
||||
}
|
||||
|
||||
canvas.style.cursor = 'default';
|
||||
|
||||
this.ctxCursor.clearRect(0, 0, this.width, this.height);
|
||||
@@ -363,7 +405,7 @@ export class SpectraDisplayer {
|
||||
this.ctx.moveTo(this.scaleX * len / 2, this.height);
|
||||
this.ctx.lineTo(this.scaleX * len / 2, this.height - (this.maxY - this.minY) * this.scaleY);
|
||||
this.ctx.strokeStyle = "red";
|
||||
this.ctx.lineWidth = 10;
|
||||
this.ctx.lineWidth = 30;
|
||||
|
||||
if (this.ctx.isPointInStroke(mx, my)) {
|
||||
this.canvas.style.cursor = 'grab';
|
||||
@@ -372,11 +414,11 @@ export class SpectraDisplayer {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.mouseFreq = null;
|
||||
this.canvas.style.cursor = 'grabbing';
|
||||
|
||||
// is dragged
|
||||
let dx = (mx - lastMouse.x) / this.scaleX;
|
||||
let dx = (mx - this.lastMouse.x) / this.scaleX;
|
||||
if (dx != 0) {
|
||||
// Set the frequency
|
||||
|
||||
@@ -399,29 +441,102 @@ export class SpectraDisplayer {
|
||||
unit: 'Hz'
|
||||
})
|
||||
|
||||
|
||||
lastMouse = { x: mx, y: my };
|
||||
this.lastMouse = { x: mx, y: my };
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', (e) => {
|
||||
|
||||
Utils.on(canvas, 'mouseup touchend', (e) => {
|
||||
if (!this.enabled) {
|
||||
let paramsEvent = {
|
||||
bubbles: e.bubbles,
|
||||
cancelable: e.cancelable,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
screenX: e.screenX,
|
||||
screenY: e.screenY,
|
||||
ctrlKey: e.ctrlKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
metaKey: e.metaKey,
|
||||
button: e.button,
|
||||
changedTouches: e.changedTouches,
|
||||
targetTouches: e.targetTouches,
|
||||
relatedTarget: e.relatedTarget,
|
||||
};
|
||||
|
||||
let touchEvent = new TouchEvent("touchend", paramsEvent);
|
||||
this.view.catalogCanvas.dispatchEvent(touchEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragging = false;
|
||||
canvas.style.cursor = 'default';
|
||||
|
||||
const clickEvent = new MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY
|
||||
});
|
||||
this.view.catalogCanvas.dispatchEvent(clickEvent);
|
||||
let mouseXY = Utils.relMouseCoords(e);
|
||||
|
||||
const timeDiff = Date.now() - mouseDownTime;
|
||||
const dx = mouseXY.x - mouseDownPos.x;
|
||||
const dy = mouseXY.y - mouseDownPos.y;
|
||||
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (timeDiff < CLICK_TIME_THRESHOLD && dist < CLICK_MOVE_THRESHOLD) {
|
||||
// Custom click detected
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mx = mouseXY.x;
|
||||
const my = mouseXY.y;
|
||||
let v = this.data.values[Math.round(mx / this.scaleX)]
|
||||
v = this.height - (v - this.minY) * this.scaleY
|
||||
|
||||
if (my >= v) {
|
||||
let dx = (mx - rect.width * 0.5) / this.scaleX;
|
||||
if (dx != 0) {
|
||||
// Set the frequency
|
||||
|
||||
// look where we are in the freq range
|
||||
let j = Utils.binarySearch(self.data.freqs, self.data.freq);
|
||||
let df, f;
|
||||
if (j > 0 && j < self.data.freqs.length - 1) {
|
||||
df = (self.data.freqs[j + 1] - self.data.freqs[j - 1]) * 0.5;
|
||||
f = self.data.freq + dx * df;
|
||||
} else if (j == 0) {
|
||||
df = self.data.freqs[1] - self.data.freqs[0]
|
||||
f = self.data.freqs[0] + dx * df;
|
||||
} else {
|
||||
df = self.data.freqs[self.data.freqs.length - 1] - self.data.freqs[self.data.freqs.length - 2];
|
||||
f = self.data.freqs[self.data.freqs.length - 1] + dx * df;
|
||||
}
|
||||
|
||||
self.hips.setFrequency({
|
||||
value: f,
|
||||
unit: 'Hz'
|
||||
})
|
||||
}
|
||||
|
||||
this.lastMouse = { x: mx, y: my };
|
||||
}
|
||||
|
||||
//this.ctxCursor.clearRect(0, 0, this.width, this.height);
|
||||
this.mouseFreq = null;
|
||||
}
|
||||
|
||||
|
||||
if (e.type !== "touchend") {
|
||||
const clickEvent = new MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY
|
||||
});
|
||||
this.view.catalogCanvas.dispatchEvent(clickEvent);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseout', (e) => {
|
||||
Utils.on(canvas, 'mouseout touchcancel', (e) => {
|
||||
this.isDragging = false;
|
||||
});
|
||||
|
||||
canvas.addEventListener('wheel', (e) => {
|
||||
Utils.on(canvas, 'wheel', (e) => {
|
||||
this.ctxCursor.clearRect(0, 0, this.width, this.height);
|
||||
|
||||
const wheelEvent = new WheelEvent('wheel', {
|
||||
@@ -535,7 +650,7 @@ export class SpectraDisplayer {
|
||||
this._redraw(this.ctx);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
window.addEventListener("spectra", this.spectraUpdateCallback);
|
||||
|
||||
this.resetScale();
|
||||
@@ -550,10 +665,12 @@ export class SpectraDisplayer {
|
||||
}
|
||||
|
||||
enableInteraction() {
|
||||
this.enabled = true;
|
||||
this.divNode.style.pointerEvents = "auto"
|
||||
}
|
||||
|
||||
disableInteraction() {
|
||||
this.enabled = false;
|
||||
this.divNode.style.pointerEvents = "none"
|
||||
}
|
||||
|
||||
@@ -591,6 +708,16 @@ export class SpectraDisplayer {
|
||||
this.ctx.lineWidth = 2;
|
||||
this.ctx.stroke();
|
||||
|
||||
this.ctxCursor.clearRect(0, 0, this.width, this.height);
|
||||
this.ctxCursor.beginPath();
|
||||
this.ctxCursor.moveTo(this.lastMouse.x, this.height);
|
||||
let v = this.data.values[Math.round(this.lastMouse.x / this.scaleX)]
|
||||
v = this.height - (v - this.minY) * this.scaleY
|
||||
this.ctxCursor.lineTo(this.lastMouse.x, v);
|
||||
this.ctxCursor.strokeStyle = "yellow";
|
||||
this.ctxCursor.lineWidth = 2;
|
||||
this.ctxCursor.stroke()
|
||||
|
||||
this._redrawLabels()
|
||||
}
|
||||
|
||||
@@ -703,7 +830,7 @@ export class SpectraDisplayer {
|
||||
|
||||
while (i < i1) {
|
||||
let y;
|
||||
const x = i * this.scaleX;
|
||||
let x = i * this.scaleX;
|
||||
|
||||
const inValidDomain = this.data.freqIdxStart !== undefined && this.data.freqIdxEnd !== undefined && i >= this.data.freqIdxStart && i <= this.data.freqIdxEnd;
|
||||
|
||||
@@ -741,8 +868,6 @@ export class SpectraDisplayer {
|
||||
|
||||
y = this.height - (array[i] - this.minY) * this.scaleY;
|
||||
|
||||
|
||||
|
||||
if (i === 0) {
|
||||
this.ctx.moveTo(x, y);
|
||||
} else {
|
||||
|
||||
@@ -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',
|
||||
|
||||
279
src/js/View.js
@@ -256,7 +256,7 @@ export let View = (function () {
|
||||
// some variables for mouse handling
|
||||
this.dragging = false;
|
||||
this.dragCoo = null;
|
||||
this.selectedLayer = 'base';
|
||||
this.selectedLayer = undefined;
|
||||
|
||||
this.needRedraw = true;
|
||||
|
||||
@@ -785,17 +785,19 @@ export let View = (function () {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
view.pinchZoomParameters.isPinching = true;
|
||||
view.pinchZoomParameters.initialZoomFactor = view.zoomFactor;
|
||||
view.pinchZoomParameters.initialDistance = Math.sqrt(Math.pow(e.targetTouches[0].clientX - e.targetTouches[1].clientX, 2) + Math.pow(e.targetTouches[0].clientY - e.targetTouches[1].clientY, 2));
|
||||
|
||||
view.fingersRotationParameters.initialViewAngleFromCenter = view.wasm.getViewCenter2NorthPoleAngle();
|
||||
view.fingersRotationParameters.initialViewAngleFromCenter = view.wasm.getRotation();
|
||||
view.fingersRotationParameters.initialFingerAngle = Math.atan2(e.targetTouches[1].clientY - e.targetTouches[0].clientY, e.targetTouches[1].clientX - e.targetTouches[0].clientX) * 180.0 / Math.PI;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
view.dragCoo = xymouse;
|
||||
view.dragPastCoo = xymouse;
|
||||
|
||||
view.dragging = true;
|
||||
|
||||
@@ -816,39 +818,6 @@ export let View = (function () {
|
||||
return true;
|
||||
});
|
||||
|
||||
/*
|
||||
Utils.on(view.catalogCanvas, "mouseup", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const xymouse = Utils.relMouseCoords(e);
|
||||
|
||||
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
|
||||
state: {
|
||||
mode: view.mode,
|
||||
dragging: view.dragging,
|
||||
rightClickPressed: view.rightClick
|
||||
},
|
||||
xy: xymouse,
|
||||
ev: e,
|
||||
});
|
||||
|
||||
if (view.rightClick) {
|
||||
if (showContextMenu) {
|
||||
view.aladin.contextMenu && view.aladin.contextMenu.show({e});
|
||||
}
|
||||
|
||||
view.rightClick = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.mode === View.SELECT) {
|
||||
view.selector.dispatch('mouseup', {coo: xymouse})
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
Utils.on(view.catalogCanvas, "click", function (e) {
|
||||
// call listener of 'click' event
|
||||
|
||||
@@ -875,6 +844,28 @@ export let View = (function () {
|
||||
|
||||
});
|
||||
|
||||
Utils.on(document, "mouseup touchend", function(e) {
|
||||
var wasDragging = view.realDragging === true;
|
||||
|
||||
if (view.dragging) { // if we were dragging, reset to default cursor
|
||||
if(view.mode === View.PAN) {
|
||||
view.setCursor('default');
|
||||
}
|
||||
|
||||
view.dragging = false;
|
||||
if (wasDragging) {
|
||||
view.realDragging = false;
|
||||
|
||||
// call the positionChanged once more with a dragging = false
|
||||
view.throttledPositionChanged(false);
|
||||
}
|
||||
|
||||
if (view.spectraDisplayer) {
|
||||
view.spectraDisplayer.enableInteraction();
|
||||
}
|
||||
} // end of "if (view.dragging) ... "
|
||||
});
|
||||
|
||||
// reacting on 'click' rather on 'mouseup' is more reliable when panning the view
|
||||
Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) {
|
||||
const xymouse = Utils.relMouseCoords(e);
|
||||
@@ -915,26 +906,27 @@ export let View = (function () {
|
||||
|
||||
var wasDragging = view.realDragging === true;
|
||||
|
||||
if (view.dragging) { // if we were dragging, reset to default cursor
|
||||
/*if (view.dragging) { // if we were dragging, reset to default cursor
|
||||
if(view.mode === View.PAN) {
|
||||
view.setCursor('default');
|
||||
}
|
||||
|
||||
view.dragging = false;
|
||||
if (view.spectraDisplayer) {
|
||||
view.spectraDisplayer.enableInteraction();
|
||||
}
|
||||
|
||||
if (wasDragging) {
|
||||
view.realDragging = false;
|
||||
|
||||
// call the positionChanged once more with a dragging = false
|
||||
view.throttledPositionChanged(false);
|
||||
}
|
||||
} // end of "if (view.dragging) ... "
|
||||
|
||||
if (view.spectraDisplayer) {
|
||||
view.spectraDisplayer.enableInteraction();
|
||||
}
|
||||
} // end of "if (view.dragging) ... "*/
|
||||
|
||||
view.mustClearCatalog = true;
|
||||
view.dragCoo = null;
|
||||
view.dragPastCoo = null;
|
||||
|
||||
if (e.type === "mouseup") {
|
||||
if (view.mode === View.SELECT) {
|
||||
@@ -1002,7 +994,9 @@ export let View = (function () {
|
||||
// TODO : remplacer par mecanisme de listeners
|
||||
// on avertit les catalogues progressifs
|
||||
view.refreshProgressiveCats();
|
||||
view.wasm.releaseLeftButtonMouse();
|
||||
if (wasDragging) {
|
||||
view.wasm.releaseLeftButtonMouse();
|
||||
}
|
||||
});
|
||||
|
||||
var lastHoveredObject; // save last object hovered by mouse
|
||||
@@ -1249,32 +1243,39 @@ export let View = (function () {
|
||||
|
||||
view.realDragging = true;
|
||||
|
||||
if (view.mode === View.PAN) {
|
||||
view.pan = {
|
||||
s1: view.dragCoo,
|
||||
s2: xymouse
|
||||
};
|
||||
}
|
||||
|
||||
var s1 = view.dragCoo, s2 = xymouse;
|
||||
// update drag coo with the new position
|
||||
view.dragCoo = xymouse;
|
||||
// update drag coo with the new position
|
||||
|
||||
/*if (view.mode == View.SELECT) {
|
||||
view.requestRedraw();
|
||||
return;
|
||||
}*/
|
||||
|
||||
if (view.mode === View.PAN) {
|
||||
view.wasm.moveMouse(s1.x, s1.y, s2.x, s2.y);
|
||||
view.wasm.goFromTo(s1.x, s1.y, s2.x, s2.y);
|
||||
|
||||
view.updateCenter();
|
||||
|
||||
ALEvent.POSITION_CHANGED.dispatchedTo(view.aladin.aladinDiv, view.viewCenter);
|
||||
|
||||
// Apply position changed callback after the move
|
||||
view.throttledPositionChanged(true);
|
||||
}
|
||||
}); //// endof mousemove ////
|
||||
|
||||
// disable text selection on IE
|
||||
//Utils.on(view.aladinDiv, "selectstart", function () { return false; })
|
||||
|
||||
view.prevWheelTime = undefined;
|
||||
|
||||
function normalizeWheel(event) {
|
||||
// Safari/Chrome on macOS: deltaMode = 0 (pixels), but trackpad steps are tiny
|
||||
let scale = 1;
|
||||
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||
scale = 16; // assume ~16px per line
|
||||
} else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
scale = window.innerHeight;
|
||||
}
|
||||
return event.deltaY * scale;
|
||||
}
|
||||
view.zoomDelta = 0;
|
||||
|
||||
Utils.on(view.catalogCanvas, 'wheel', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -1295,24 +1296,13 @@ export let View = (function () {
|
||||
if (typeof onWheelTriggeredFunction === 'function') {
|
||||
onWheelTriggeredFunction(e)
|
||||
} else {
|
||||
// Default Aladin Lite zooming
|
||||
view.delta = e.deltaY || e.detail || (-e.wheelDelta);
|
||||
|
||||
if (!view.throttledTouchPadZoom) {
|
||||
view.throttledTouchPadZoom = () => {
|
||||
const factor = Utils.detectTrackPad(e) ? 1.05 : 1.2;
|
||||
const currZoomFactor = view.zoom.isZooming ? view.zoom.finalZoom : view.zoomFactor;
|
||||
let newZoomFactor = view.delta > 0 ? currZoomFactor * factor : currZoomFactor / factor;
|
||||
|
||||
// inside case
|
||||
view.zoom.apply({
|
||||
stop: newZoomFactor,
|
||||
duration: 100,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
view.throttledTouchPadZoom();
|
||||
// Default Aladin Lite zooming
|
||||
const normalizedDelta = e.deltaY && normalizeWheel(e) || e.detail || (-e.wheelDelta);
|
||||
// Accumulate the normalized delta
|
||||
// We do not zoom because we cannot rely on "wheel" event
|
||||
// being triggered at constant time steps
|
||||
// The zoom is delayed to the redraw which is animation frame requested!
|
||||
view.zoomDelta += normalizedDelta;
|
||||
|
||||
if (view.mode === View.TOOL_COLOR_PICKER) {
|
||||
pickColor(xymouse);
|
||||
@@ -1386,17 +1376,44 @@ export let View = (function () {
|
||||
/**
|
||||
* redraw the whole view
|
||||
*/
|
||||
View.prototype.redraw = function (timestamp) {
|
||||
// request another frame
|
||||
requestAnimFrame(this.redrawClbk);
|
||||
|
||||
View.prototype.redraw = function (now) {
|
||||
// Elapsed time since last loop
|
||||
const now = performance.now();
|
||||
const elapsedTime = now - timestamp;
|
||||
this.dt = elapsedTime;
|
||||
const elapsedTime = now - this.prevTime;
|
||||
this.prevTime = now;
|
||||
|
||||
if (Math.abs(this.zoomDelta) > 1e-3) {
|
||||
// Apply a fraction each frame (smoothing)
|
||||
let step = this.zoomDelta * 0.2;
|
||||
function wheelToZoomFactor(delta) {
|
||||
const sensitivity = 0.002; // tune this
|
||||
return Math.exp(-delta * sensitivity);
|
||||
}
|
||||
|
||||
this.zoomFactor /= wheelToZoomFactor(step);
|
||||
this.zoomDelta -= step;
|
||||
}
|
||||
|
||||
if (this.pan) {
|
||||
let s1 = this.pan.s1;
|
||||
let s2 = this.pan.s2;
|
||||
|
||||
if (s1 && s2) {
|
||||
this.wasm.moveMouse(s1.x, s1.y, s2.x, s2.y);
|
||||
this.wasm.goFromTo(s1.x, s1.y, s2.x, s2.y);
|
||||
|
||||
this.updateCenter();
|
||||
|
||||
ALEvent.POSITION_CHANGED.dispatchedTo(this.aladin.aladinDiv, this.viewCenter);
|
||||
|
||||
// Apply position changed callback after the move
|
||||
this.throttledPositionChanged(true);
|
||||
}
|
||||
|
||||
this.pan = null;
|
||||
}
|
||||
|
||||
this.moving = this.wasm.update(elapsedTime);
|
||||
|
||||
|
||||
// inertia run throttled position
|
||||
if (this.moving && this.aladin.callbacksByEventName && this.aladin.callbacksByEventName['positionChanged'] && this.wasm.isInerting()) {
|
||||
// run the trottled position
|
||||
@@ -1409,6 +1426,9 @@ export let View = (function () {
|
||||
this.drawAllOverlays();
|
||||
}
|
||||
this.needRedraw = false;
|
||||
|
||||
// request another frame
|
||||
requestAnimFrame(this.redrawClbk);
|
||||
};
|
||||
|
||||
View.prototype.drawAllOverlays = function () {
|
||||
@@ -1686,6 +1706,10 @@ export let View = (function () {
|
||||
}
|
||||
|
||||
View.prototype.setRotation = function(rotation) {
|
||||
if (Math.abs(rotation - this.aladin.getRotation()) < 1e-5) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.wasm.setRotation(rotation);
|
||||
var rotationChangedCallback = this.aladin.callbacksByEventName["rotationChanged"];
|
||||
typeof rotationChangedCallback === "function" && rotationChangedCallback(rotation);
|
||||
@@ -1760,6 +1784,27 @@ export let View = (function () {
|
||||
// register its promise
|
||||
this.imageLayersBeingQueried.set(layer, imageLayer);
|
||||
|
||||
// Check whether this layer already exist
|
||||
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layer);
|
||||
let alreadyPresentImageLayer;
|
||||
if (idxOverlayLayer == -1) {
|
||||
// it does not exist so we add it to the stack
|
||||
this.overlayLayers.push(layer);
|
||||
} else {
|
||||
// it exists
|
||||
alreadyPresentImageLayer = this.imageLayers.get(layer);
|
||||
|
||||
if (alreadyPresentImageLayer) {
|
||||
if (alreadyPresentImageLayer.added === true) {
|
||||
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
|
||||
}
|
||||
|
||||
alreadyPresentImageLayer.added = false;
|
||||
}
|
||||
// Notify that this image layer has been replaced by the wasm part
|
||||
this.imageLayers.delete(layer);
|
||||
}
|
||||
|
||||
this.addImageLayer(imageLayer, layer);
|
||||
|
||||
return imageLayer;
|
||||
@@ -1769,33 +1814,13 @@ export let View = (function () {
|
||||
View.prototype._addLayer = function(imageLayer) {
|
||||
// Keep the JS frontend in-line with the wasm state
|
||||
const layerName = imageLayer.layer;
|
||||
// Check whether this layer already exist
|
||||
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layerName);
|
||||
let alreadyPresentImageLayer;
|
||||
if (idxOverlayLayer == -1) {
|
||||
// it does not exist so we add it to the stack
|
||||
this.overlayLayers.push(layerName);
|
||||
} else {
|
||||
// it exists
|
||||
alreadyPresentImageLayer = this.imageLayers.get(layerName);
|
||||
|
||||
// Notify that this image layer has been replaced by the wasm part
|
||||
if (alreadyPresentImageLayer && alreadyPresentImageLayer.added === true) {
|
||||
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
|
||||
}
|
||||
|
||||
alreadyPresentImageLayer.added = false;
|
||||
this.imageLayers.delete(layerName);
|
||||
}
|
||||
|
||||
imageLayer.added = true;
|
||||
|
||||
this.imageLayers.set(layerName, imageLayer);
|
||||
|
||||
// select the layer if he is on top
|
||||
//if (idxOverlayLayer == -1) {
|
||||
this.selectLayer(layerName);
|
||||
//}
|
||||
this.selectLayer(layerName);
|
||||
|
||||
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
|
||||
}
|
||||
@@ -1853,7 +1878,7 @@ export let View = (function () {
|
||||
// Remove the settled promise
|
||||
this.promises.splice(idx, 1);
|
||||
|
||||
const noMoreLayersToWaitFor = this.promises.length === 0;
|
||||
/*const noMoreLayersToWaitFor = this.promises.length === 0;
|
||||
|
||||
if (noMoreLayersToWaitFor) {
|
||||
if (self.empty) {
|
||||
@@ -1863,48 +1888,19 @@ export let View = (function () {
|
||||
// it the best I can do if the MOCServer is out
|
||||
self.aladin.setBaseImageLayer("https://alaskybis.cds.unistra.fr/DSS/DSSColor/");
|
||||
} else {
|
||||
// there is surveys that have been queried
|
||||
// rename the first overlay layer to "base"
|
||||
self.renameLayer(this.overlayLayers[0], "base");
|
||||
//self.renameLayer(this.overlayLayers[0], "base");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
})
|
||||
}
|
||||
|
||||
// The survey at layer must have been added to the view!
|
||||
View.prototype.renameLayer = function(layer, newLayer) {
|
||||
if (layer === newLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Throw an exception if either the first or the second layers are not in the stack
|
||||
this.wasm.renameLayer(layer, newLayer);
|
||||
|
||||
let imageLayer = this.imageLayers.get(layer);
|
||||
imageLayer.layer = newLayer;
|
||||
|
||||
// Change in overlaylayers
|
||||
const idx = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layer);
|
||||
this.overlayLayers[idx] = newLayer;
|
||||
// Change in imageLayers
|
||||
this.imageLayers.delete(layer);
|
||||
this.imageLayers.set(newLayer, imageLayer);
|
||||
|
||||
if (this.selectedLayer === layer) {
|
||||
this.selectedLayer = newLayer;
|
||||
}
|
||||
|
||||
// Tell the layer hierarchy has changed
|
||||
ALEvent.HIPS_LAYER_RENAMED.dispatchedTo(this.aladinDiv, { layer, newLayer });
|
||||
}
|
||||
|
||||
View.prototype.swapLayers = function(firstLayer, secondLayer) {
|
||||
// Throw an exception if either the first or the second layers are not in the stack
|
||||
this.wasm.swapLayers(firstLayer, secondLayer);
|
||||
|
||||
// Swap in overlaylayers
|
||||
const idxFirstLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == firstLayer);
|
||||
const idxSecondLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == secondLayer);
|
||||
const idxFirstLayer = this.overlayLayers.indexOf(firstLayer);
|
||||
const idxSecondLayer = this.overlayLayers.indexOf(secondLayer);
|
||||
|
||||
const tmp = this.overlayLayers[idxFirstLayer];
|
||||
this.overlayLayers[idxFirstLayer] = this.overlayLayers[idxSecondLayer];
|
||||
@@ -1943,7 +1939,7 @@ export let View = (function () {
|
||||
this.overlayLayers.splice(idxOverlaidLayer, 1);
|
||||
|
||||
if (this.overlayLayers.length === 0) {
|
||||
this.empty = true;
|
||||
//this.empty = true;
|
||||
} else if (this.selectedLayer === layer) {
|
||||
// If the layer removed was selected then we select the last layer
|
||||
this.selectLayer(this.overlayLayers[this.overlayLayers.length - 1]);
|
||||
@@ -1952,12 +1948,12 @@ export let View = (function () {
|
||||
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
|
||||
|
||||
// check if there are no more surveys
|
||||
const noMoreLayersToWaitFor = this.promises.length === 0;
|
||||
/*const noMoreLayersToWaitFor = this.promises.length === 0;
|
||||
if (noMoreLayersToWaitFor && this.empty) {
|
||||
// no promises to launch!
|
||||
const dssId = Aladin.DEFAULT_OPTIONS.survey;
|
||||
this.aladin.setBaseImageLayer(dssId);
|
||||
}
|
||||
}*/
|
||||
};
|
||||
|
||||
View.prototype.contains = function(survey) {
|
||||
@@ -2004,7 +2000,8 @@ export let View = (function () {
|
||||
}
|
||||
}
|
||||
|
||||
View.prototype.getImageLayer = function (layer = "base") {
|
||||
View.prototype.getImageLayer = function (layer) {
|
||||
layer = layer || (this.overlayLayers && this.overlayLayers[0]);
|
||||
let imageLayerQueried = this.imageLayersBeingQueried.get(layer);
|
||||
let imageLayer = this.imageLayers.get(layer);
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ import { Utils } from "../../Utils.ts";
|
||||
import { ActionButton } from "../Widgets/ActionButton.js";
|
||||
import infoIconUrl from "../../../../assets/icons/info.svg"
|
||||
import { Icon } from "../Widgets/Icon.js";
|
||||
import { Tree } from "../Widgets/Tree.js";
|
||||
import { ALEvent } from "../../events/ALEvent.js";
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
@@ -44,27 +46,119 @@ import { Icon } from "../Widgets/Icon.js";
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
function fillHiPSHierarchy(name, hips, path, hierarchy) {
|
||||
let folders = path.split('/')
|
||||
let curFolder = folders.shift()
|
||||
|
||||
if(curFolder === 'Image') {
|
||||
let newPath = folders.join('/')
|
||||
fillHiPSHierarchy(name, hips, newPath, hierarchy);
|
||||
} else {
|
||||
// Some exceptions because the MOCServer client_category field may contain some typos
|
||||
if (['X', 'X-ray', 'Xray'].includes(curFolder)) {
|
||||
curFolder = 'X-ray'
|
||||
}
|
||||
|
||||
if (['Radion', 'Radio'].includes(curFolder)) {
|
||||
curFolder = 'Radio'
|
||||
}
|
||||
|
||||
if (curFolder === "Deprecated")
|
||||
return;
|
||||
|
||||
hierarchy[curFolder] = hierarchy[curFolder] || {};
|
||||
if (folders.length == 0) {
|
||||
hierarchy[curFolder][name] = hips
|
||||
} else {
|
||||
let newPath = folders.join('/')
|
||||
fillHiPSHierarchy(name, hips, newPath, hierarchy[curFolder])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HiPSBrowserBox extends Box {
|
||||
static HiPSList = {};
|
||||
|
||||
constructor(aladin, options) {
|
||||
let self;
|
||||
|
||||
let filter = (item, params) => {
|
||||
if (params.regime) {
|
||||
if (!item.obs_regime)
|
||||
return false;
|
||||
|
||||
if (params.regime.toLowerCase() !== item.obs_regime.toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.resolution) {
|
||||
if (!item.hips_tile_width || !item.hips_order) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let pixelHEALPixOrder = Math.log2(item.hips_tile_width) + (+item.hips_order);
|
||||
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
|
||||
|
||||
if (resPixel > params.resolution)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params.title) {
|
||||
if (!item.obs_title)
|
||||
return false;
|
||||
|
||||
if (!item.obs_title.toLowerCase().includes(params.title.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Search tree
|
||||
let searchTree = new Tree({
|
||||
// a JS object describing the tree to show
|
||||
label: (item) => {
|
||||
let name = item.obs_title.replace(/:|\'/g, '');
|
||||
return name;
|
||||
},
|
||||
// a callback called when the user selects a leaf item of the tree
|
||||
click: (item) => {
|
||||
let image = item.ID || item.hips_service_url;
|
||||
let name = item.obs_title || item.ID;
|
||||
self._addHiPS(image, name)
|
||||
},
|
||||
// a callback called for filtering
|
||||
filter,
|
||||
});
|
||||
|
||||
MocServer.getAllHiPSes().then((HiPSes) => {
|
||||
HiPSBrowserBox.HiPSList = {}
|
||||
|
||||
let hipsHierarchy = {};
|
||||
// Fill the HiPSList from the MOCServer
|
||||
|
||||
// Build a hierarchy w.r.t sorted by regime
|
||||
HiPSes.forEach((h) => {
|
||||
let name = h.obs_title;
|
||||
name = name.replace(/:|\'/g, '');
|
||||
|
||||
HiPSBrowserBox.HiPSList[name] = h;
|
||||
|
||||
if (h.client_category) {
|
||||
let path = h.client_category
|
||||
|
||||
fillHiPSHierarchy(name, h, path, hipsHierarchy)
|
||||
}
|
||||
});
|
||||
|
||||
self.searchTree.setHierarchy(hipsHierarchy)
|
||||
|
||||
// Initialize the autocompletion without any filtering
|
||||
self._filterHiPSList({})
|
||||
});
|
||||
|
||||
|
||||
|
||||
const _parseHiPS = (e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
@@ -88,7 +182,6 @@ export class HiPSBrowserBox extends Box {
|
||||
|
||||
if (image) {
|
||||
self._addHiPS(image, name)
|
||||
self.searchDropdown.update({title: value});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,19 +211,17 @@ export class HiPSBrowserBox extends Box {
|
||||
disable: true,
|
||||
})
|
||||
|
||||
self.searchTree.triggerFilter({title: e.target.value});
|
||||
|
||||
searchDropdown.removeClass('aladin-valid')
|
||||
searchDropdown.removeClass('aladin-not-valid')
|
||||
},
|
||||
change(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault()
|
||||
_parseHiPS(e)
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let filterEnabler = Input.checkbox({
|
||||
name: "filter-enabler",
|
||||
tooltip: { content: "enable/disable" },
|
||||
checked: false,
|
||||
click(e) {
|
||||
let on = e.target.checked;
|
||||
@@ -182,13 +273,9 @@ export class HiPSBrowserBox extends Box {
|
||||
},
|
||||
toggled: false,
|
||||
actionOn: (e) => {
|
||||
self.filterBox._show({
|
||||
position: {
|
||||
nextTo: filterBtn,
|
||||
direction: "bottom",
|
||||
aladin,
|
||||
},
|
||||
});
|
||||
self.filterBox._show({position: {
|
||||
anchor: 'right center'
|
||||
}});
|
||||
},
|
||||
actionOff: (e) => {
|
||||
self.filterBox._hide();
|
||||
@@ -205,15 +292,17 @@ export class HiPSBrowserBox extends Box {
|
||||
size: 'medium',
|
||||
url: hipsIconUrl,
|
||||
monochrome: true,
|
||||
}), "HiPS browser"])
|
||||
},
|
||||
onDragged: () => {
|
||||
if (self.filterBtn.toggled) {
|
||||
self.filterBtn.toggle();
|
||||
}
|
||||
}), "HiPS browser"]),
|
||||
draggable: true,
|
||||
},
|
||||
//onDragged: () => {
|
||||
//if (self.filterBtn.toggled) {
|
||||
//self.filterBtn.toggle();
|
||||
//}
|
||||
//},
|
||||
classList: ['aladin-HiPS-browser-box'],
|
||||
content: Layout.vertical([
|
||||
searchTree,
|
||||
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
|
||||
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn, filterNumberElt])]),
|
||||
]),
|
||||
@@ -222,6 +311,9 @@ export class HiPSBrowserBox extends Box {
|
||||
aladin.aladinDiv
|
||||
);
|
||||
|
||||
self = this;
|
||||
|
||||
this.searchTree = searchTree;
|
||||
this.filterBox = new HiPSFilterBox(aladin, {
|
||||
callback: (params) => {
|
||||
self._filterHiPSList(params);
|
||||
@@ -236,46 +328,42 @@ export class HiPSBrowserBox extends Box {
|
||||
|
||||
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
|
||||
|
||||
self = this;
|
||||
this.filter = filter;
|
||||
|
||||
this.filterCallback = (HiPS, params) => {
|
||||
if (params.regime) {
|
||||
if (!HiPS.obs_regime)
|
||||
return false;
|
||||
filterEnabler.action({target: {checked: true}});
|
||||
|
||||
if (params.regime.toLowerCase() !== HiPS.obs_regime.toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this._addListeners();
|
||||
|
||||
if (params.spatial) {
|
||||
if (!HiPS.ID)
|
||||
return false;
|
||||
this._requestMOCServer();
|
||||
}
|
||||
|
||||
if (Array.isArray(params.spatial) && !(params.spatial.includes(HiPS.ID))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_addListeners() {
|
||||
const requestMOCServerDebounced = Utils.debounce(() => {
|
||||
this._requestMOCServer()
|
||||
}, 500);
|
||||
|
||||
if (params.resolution) {
|
||||
if (!HiPS.hips_tile_width || !HiPS.hips_order) {
|
||||
return false;
|
||||
}
|
||||
ALEvent.POSITION_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
|
||||
ALEvent.ZOOM_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
|
||||
}
|
||||
|
||||
let pixelHEALPixOrder = Math.log2(HiPS.hips_tile_width) + (+HiPS.hips_order);
|
||||
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
|
||||
_requestMOCServer() {
|
||||
if (this.isHidden && this.searchTree) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resPixel > params.resolution)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
let self = this;
|
||||
MocServer.getAllHiPSesInsideView(this.aladin)
|
||||
.then((HiPSes) => {
|
||||
let HiPSIDs = HiPSes.map((x) => x.ID);
|
||||
self.searchTree.highlightNodes(HiPSIDs)
|
||||
})
|
||||
}
|
||||
|
||||
_addHiPS(id, name) {
|
||||
let self = this;
|
||||
|
||||
self.searchDropdown.update({value: name, title: name});
|
||||
|
||||
let hips = A.imageHiPS(id, {
|
||||
name,
|
||||
successCallback: (hips) => {
|
||||
@@ -401,8 +489,8 @@ export class HiPSBrowserBox extends Box {
|
||||
let HiPS = HiPSBrowserBox.HiPSList[key];
|
||||
// apply filtering
|
||||
if (
|
||||
self.filterCallback &&
|
||||
self.filterCallback(HiPS, params)
|
||||
self.filter &&
|
||||
self.filter(HiPS, params)
|
||||
) {
|
||||
// search with the name or id
|
||||
let name = HiPS.obs_title;
|
||||
@@ -412,14 +500,15 @@ export class HiPSBrowserBox extends Box {
|
||||
}
|
||||
}
|
||||
|
||||
if (self.searchTree) {
|
||||
self.searchTree.triggerFilter(params);
|
||||
}
|
||||
|
||||
self.searchDropdown.update({ options: HiPSIDs });
|
||||
self.filterNumberElt.innerHTML = HiPSIDs.length + "/" + Object.keys(HiPSBrowserBox.HiPSList).length;
|
||||
}
|
||||
|
||||
_hide() {
|
||||
if (this.filterBox)
|
||||
this.filterBox.signalBrowserStatus(true)
|
||||
|
||||
if (this.filterBtn && this.filterBtn.toggled) {
|
||||
this.filterBtn.toggle();
|
||||
}
|
||||
@@ -428,12 +517,10 @@ export class HiPSBrowserBox extends Box {
|
||||
}
|
||||
|
||||
_show(options) {
|
||||
this._requestMOCServer();
|
||||
|
||||
// Regenerate a new layer name
|
||||
this.layer = Utils.uuidv4()
|
||||
|
||||
if (this.filterBox)
|
||||
this.filterBox.signalBrowserStatus(false)
|
||||
|
||||
this.layer = (options && options.layer) || Utils.uuidv4();
|
||||
super._show(options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,11 @@
|
||||
|
||||
import { Box } from "../Widgets/Box.js";
|
||||
import { Form } from "../Widgets/Form.js";
|
||||
import { MocServer } from "../../MocServer.js";
|
||||
import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
import { Layout } from "../Layout.js";
|
||||
import { Angle } from "../../libs/astro/angle.js";
|
||||
import { ALEvent } from "../../events/ALEvent.js";
|
||||
import { Utils } from "../../Utils.ts";
|
||||
import { AladinUtils } from "../../AladinUtils.js";
|
||||
import { Input } from "../Widgets/Input.js";
|
||||
import freqIconUrl from '../../../../assets/icons/freq.svg';
|
||||
import inViewIconUrl from '../../../../assets/icons/inside.svg';
|
||||
import targetIconUrl from '../../../../assets/icons/target.svg';
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
@@ -45,120 +39,96 @@ export class HiPSFilterBox extends Box {
|
||||
constructor(aladin, options) {
|
||||
let self;
|
||||
|
||||
let regimeBtn = new TogglerActionButton({
|
||||
content: 'Freq',
|
||||
icon: {
|
||||
monochrome: true,
|
||||
size: 'medium',
|
||||
url: freqIconUrl,
|
||||
},
|
||||
tooltip: {content: 'Observation bandwidth', position: {direction: 'bottom'}},
|
||||
toggled: true,
|
||||
actionOn: () => {
|
||||
self._triggerFilteringCallback();
|
||||
},
|
||||
actionOff: () => {
|
||||
let regimeBtn = Input.checkbox({
|
||||
name: 'Freq',
|
||||
tooltip: {content: 'Observation bandwidth', position: {direction: 'left'}},
|
||||
type: 'checkbox',
|
||||
checked: false,
|
||||
click(e) {
|
||||
self._triggerFilteringCallback();
|
||||
}
|
||||
});
|
||||
let spatialBtn = new TogglerActionButton({
|
||||
content: 'In view',
|
||||
icon: {
|
||||
monochrome: true,
|
||||
size: 'medium',
|
||||
url: inViewIconUrl,
|
||||
},
|
||||
tooltip: {content: 'Survey in view only!', position: {direction: 'bottom'}},
|
||||
toggled: false,
|
||||
actionOn: () => {
|
||||
self._requestMOCServer();
|
||||
},
|
||||
actionOff: () => {
|
||||
self._triggerFilteringCallback();
|
||||
}
|
||||
});
|
||||
let resolutionBtn = new TogglerActionButton({
|
||||
content: 'Resolution',
|
||||
icon: {
|
||||
monochrome: true,
|
||||
size: 'medium',
|
||||
url: targetIconUrl,
|
||||
},
|
||||
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'bottom'}},
|
||||
toggled: false,
|
||||
actionOn: () => {
|
||||
self._triggerFilteringCallback();
|
||||
},
|
||||
actionOff: () => {
|
||||
let resolutionBtn = Input.checkbox({
|
||||
name: 'Resolution',
|
||||
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'left'}},
|
||||
type: 'checkbox',
|
||||
checked: false,
|
||||
click(e) {
|
||||
self._triggerFilteringCallback();
|
||||
}
|
||||
});
|
||||
|
||||
let logSlider = new Input({
|
||||
let regimeOption = Layout.horizontal({
|
||||
tooltip: {
|
||||
content: "Observation regime",
|
||||
position: { direction: "right" },
|
||||
},
|
||||
label: 'Freq: ',
|
||||
layout: [Input.select({
|
||||
value: "Optical",
|
||||
options: [
|
||||
"Radio",
|
||||
"Infrared",
|
||||
"Millimeter",
|
||||
"Optical",
|
||||
"UV",
|
||||
"EUV",
|
||||
"X-ray",
|
||||
"Gamma-ray",
|
||||
],
|
||||
change: (e) => {
|
||||
let regime = e.target.value;
|
||||
self.params["regime"] = regime;
|
||||
|
||||
self._triggerFilteringCallback();
|
||||
},
|
||||
}), regimeBtn]
|
||||
});
|
||||
|
||||
let resolutionOption = Layout.horizontal({
|
||||
label: "Max resolution [°/px]:",
|
||||
name: "res",
|
||||
value: 0.1,
|
||||
type: 'range',
|
||||
cssStyle: {
|
||||
width: '100%'
|
||||
},
|
||||
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
|
||||
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
|
||||
stretch: "log",
|
||||
min: 0.1 / 3600,
|
||||
max: 0.1,
|
||||
reversed: true,
|
||||
change: (e, slider, deg) => {
|
||||
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
|
||||
layout: [
|
||||
new Input({
|
||||
name: "res",
|
||||
value: 0.1,
|
||||
type: 'range',
|
||||
cssStyle: {
|
||||
width: '200px'
|
||||
},
|
||||
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
|
||||
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
|
||||
stretch: "log",
|
||||
min: 0.1 / 3600,
|
||||
max: 0.1,
|
||||
reversed: true,
|
||||
change: (e, slider, deg) => {
|
||||
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
|
||||
|
||||
let resolution = new Angle(deg);
|
||||
self.params["resolution"] = resolution.degrees();
|
||||
let resolution = new Angle(deg);
|
||||
self.params["resolution"] = resolution.degrees();
|
||||
|
||||
self._triggerFilteringCallback();
|
||||
},
|
||||
self._triggerFilteringCallback();
|
||||
},
|
||||
}),
|
||||
resolutionBtn,
|
||||
]
|
||||
});
|
||||
super(
|
||||
{
|
||||
classList: ['aladin-HiPS-filter-box'],
|
||||
header: {
|
||||
title: 'Filter tags',
|
||||
draggable: false,
|
||||
},
|
||||
close: false,
|
||||
classList: ['aladin-HiPS-filter-box'],
|
||||
content: Layout.vertical([
|
||||
'<b>Filter by:</b>',
|
||||
Layout.horizontal([regimeBtn, spatialBtn, resolutionBtn]),
|
||||
'<b>Details:</b>',
|
||||
new Form({
|
||||
subInputs: [
|
||||
{
|
||||
type: "group",
|
||||
subInputs: [
|
||||
{
|
||||
label: "Freq:",
|
||||
name: "regime",
|
||||
value: "Optical",
|
||||
type: 'select',
|
||||
options: [
|
||||
"Radio",
|
||||
"Infrared",
|
||||
"Millimeter",
|
||||
"Optical",
|
||||
"UV",
|
||||
"EUV",
|
||||
"X-ray",
|
||||
"Gamma-ray",
|
||||
],
|
||||
change: (e) => {
|
||||
let regime = e.target.value;
|
||||
self.params["regime"] = regime;
|
||||
|
||||
//regimeBtn.update({content: regime});
|
||||
|
||||
self._triggerFilteringCallback();
|
||||
},
|
||||
tooltip: {
|
||||
content: "Observation regime",
|
||||
position: { direction: "right" },
|
||||
},
|
||||
},
|
||||
logSlider
|
||||
regimeOption,
|
||||
resolutionOption
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -170,60 +140,28 @@ export class HiPSFilterBox extends Box {
|
||||
|
||||
self = this;
|
||||
|
||||
this.browserClosed = false;
|
||||
|
||||
this.callback = options.callback;
|
||||
|
||||
this.regimeBtn = regimeBtn;
|
||||
this.spatialBtn = spatialBtn;
|
||||
this.resolutionBtn = resolutionBtn;
|
||||
|
||||
this.params = {
|
||||
regime: "Optical",
|
||||
spatial: true,
|
||||
highlight: true,
|
||||
resolution: 1, // 1°/pixel
|
||||
};
|
||||
this.on = false;
|
||||
this.aladin = aladin;
|
||||
this._addListeners();
|
||||
}
|
||||
|
||||
_addListeners() {
|
||||
const requestMOCServerDebounced = Utils.debounce(() => {
|
||||
this._requestMOCServer()
|
||||
}, 500);
|
||||
|
||||
ALEvent.POSITION_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
|
||||
ALEvent.ZOOM_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
|
||||
}
|
||||
|
||||
_requestMOCServer() {
|
||||
if (!this.spatialBtn.toggled || !this.on || this.browserClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let self = this;
|
||||
MocServer.getAllHiPSesInsideView(this.aladin)
|
||||
.then((HiPSes) => {
|
||||
let HiPSIDs = HiPSes.map((x) => x.ID);
|
||||
self.params["spatial"] = HiPSIDs;
|
||||
|
||||
self._triggerFilteringCallback();
|
||||
})
|
||||
}
|
||||
|
||||
_triggerFilteringCallback() {
|
||||
let filterParams = {};
|
||||
|
||||
if (this.regimeBtn.toggled) {
|
||||
if (this.regimeBtn.checked) {
|
||||
filterParams['regime'] = this.params['regime']
|
||||
}
|
||||
|
||||
if (this.spatialBtn.toggled) {
|
||||
filterParams['spatial'] = this.params['spatial']
|
||||
}
|
||||
|
||||
if (this.resolutionBtn.toggled) {
|
||||
if (this.resolutionBtn.checked) {
|
||||
filterParams['resolution'] = this.params['resolution']
|
||||
}
|
||||
|
||||
@@ -232,21 +170,18 @@ export class HiPSFilterBox extends Box {
|
||||
}
|
||||
}
|
||||
|
||||
signalBrowserStatus(closed) {
|
||||
/*signalBrowserStatus(closed) {
|
||||
this.browserClosed = closed;
|
||||
|
||||
// open
|
||||
if (!closed) {
|
||||
this._requestMOCServer()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
enable(enable) {
|
||||
this.on = enable;
|
||||
|
||||
if (this.on)
|
||||
this._requestMOCServer();
|
||||
|
||||
this._triggerFilteringCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,11 +219,6 @@ import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
value: 0.0,
|
||||
change: (e) => {
|
||||
let minCut = +e.target.value
|
||||
let imgFormat = self.options.layer.imgFormat;
|
||||
if (imgFormat !== "fits") {
|
||||
minCut /= 255.0;
|
||||
}
|
||||
|
||||
self.options.layer.setCuts(minCut, self.options.layer.getColorCfg().getCuts()[1])
|
||||
}
|
||||
},
|
||||
@@ -238,12 +233,6 @@ import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
value: 1.0,
|
||||
change: (e) => {
|
||||
let maxCut = +e.target.value
|
||||
|
||||
let imgFormat = self.options.layer.imgFormat;
|
||||
if (imgFormat !== "fits") {
|
||||
maxCut /= 255.0;
|
||||
}
|
||||
|
||||
self.options.layer.setCuts(self.options.layer.getColorCfg().getCuts()[0], maxCut)
|
||||
}
|
||||
}]
|
||||
@@ -251,7 +240,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
|
||||
let colorSettingsContent = new Form({
|
||||
subInputs: [{
|
||||
label: 'colormap:',
|
||||
label: 'cmap:',
|
||||
type: 'select',
|
||||
name: 'cmap',
|
||||
value: 'native',
|
||||
@@ -302,21 +291,23 @@ import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
let reversed = colorCfg.getReversed();
|
||||
|
||||
let [minCut, maxCut] = colorCfg.getCuts();
|
||||
if (layer.imgFormat !== "fits") {
|
||||
minCut = Math.round(minCut * 255);
|
||||
maxCut = Math.round(maxCut * 255);
|
||||
}
|
||||
this.pixelSettingsContent.set('mincut', +minCut.toFixed(4))
|
||||
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
|
||||
if (minCut)
|
||||
this.pixelSettingsContent.set('mincut', +minCut.toFixed(4))
|
||||
if (maxCut)
|
||||
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
|
||||
|
||||
this.pixelSettingsContent.set('stretch', stretch)
|
||||
let fmtInput = this.pixelSettingsContent.getInput('fmt')
|
||||
|
||||
fmtInput.innerHTML = '';
|
||||
|
||||
for (const option of layer.getAvailableFormats()) {
|
||||
fmtInput.innerHTML += "<option>" + option + "</option>";
|
||||
if (layer.getAvailableFormats()) {
|
||||
|
||||
for (const option of layer.getAvailableFormats()) {
|
||||
fmtInput.innerHTML += "<option>" + option + "</option>";
|
||||
}
|
||||
fmtInput.value = layer.imgFormat;
|
||||
}
|
||||
fmtInput.value = layer.imgFormat;
|
||||
|
||||
|
||||
this.colorSettingsContent.set('cmap', colormap);
|
||||
this.colorSettingsContent.set('reverse', reversed);
|
||||
@@ -331,7 +322,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
update(options) {
|
||||
if (options.layer) {
|
||||
let self = this;
|
||||
if (options.layer.isSpectralCube()) {
|
||||
if (options.layer.isSpectralCube && options.layer.isSpectralCube()) {
|
||||
let spectraDisplayer = self.aladin.view.spectraDisplayer;
|
||||
|
||||
self.spectraBtn = new TogglerActionButton({
|
||||
|
||||
@@ -43,8 +43,7 @@ import removeIconUrl from "../../../../assets/icons/remove.svg";
|
||||
import settingsIconUrl from "../../../../assets/icons/settings.svg";
|
||||
import searchIconImg from "../../../../assets/icons/search.svg";
|
||||
import downloadIconUrl from '../../../../assets/icons/download.svg';
|
||||
|
||||
|
||||
import swapIcon from '../../../../assets/icons/swap.svg'
|
||||
import { TogglerActionButton } from "../Button/Toggler.js";
|
||||
import { Icon } from "../Widgets/Icon.js";
|
||||
import { Box } from "../Widgets/Box.js";
|
||||
@@ -75,7 +74,7 @@ export class OverlayStackBox extends Box {
|
||||
};*/
|
||||
static predefinedCats = {
|
||||
simbad: {
|
||||
url: "https://axel.u-strasbg.fr/HiPSCatService/SIMBAD",
|
||||
url: "https://axel.cds.unistra.fr/HiPSCatService/SIMBAD",
|
||||
options: {
|
||||
id: "simbad",
|
||||
name: "SIMBAD",
|
||||
@@ -98,7 +97,7 @@ export class OverlayStackBox extends Box {
|
||||
},
|
||||
},
|
||||
gaia: {
|
||||
url: "https://axel.u-strasbg.fr/HiPSCatService/I/355/gaiadr3",
|
||||
url: "https://axel.cds.unistra.fr/HiPSCatService/I/355/gaiadr3",
|
||||
options: {
|
||||
id: "gaia-dr3",
|
||||
name: "Gaia DR3",
|
||||
@@ -109,7 +108,7 @@ export class OverlayStackBox extends Box {
|
||||
},
|
||||
},
|
||||
twomass: {
|
||||
url: "https://axel.u-strasbg.fr/HiPSCatService/II/246/out",
|
||||
url: "https://axel.cds.unistra.fr/HiPSCatService/II/246/out",
|
||||
options: {
|
||||
id: "2mass",
|
||||
name: "2MASS",
|
||||
@@ -731,6 +730,8 @@ export class OverlayStackBox extends Box {
|
||||
// one must add the current HiPS too!
|
||||
favoritesCopy.sort();
|
||||
|
||||
favoritesCopy.push("More...")
|
||||
|
||||
hips.HiPSSelector.update({value: currentHiPS, options: favoritesCopy});
|
||||
}
|
||||
});
|
||||
@@ -745,14 +746,6 @@ export class OverlayStackBox extends Box {
|
||||
}
|
||||
}
|
||||
|
||||
/*if (this.hipsBrowser) {
|
||||
this.hipsBrowser._hide();
|
||||
}*/
|
||||
|
||||
/*if (this.catBox) {
|
||||
this.catBox._hide();
|
||||
}*/
|
||||
|
||||
if (this.addOverlayBtn) this.addOverlayBtn.hideMenu();
|
||||
|
||||
if (this.addHiPSBtn) this.addHiPSBtn.hideMenu();
|
||||
@@ -781,7 +774,6 @@ export class OverlayStackBox extends Box {
|
||||
})
|
||||
);
|
||||
layout = layout.concat(this._createSurveysList());
|
||||
|
||||
return Layout.vertical({ layout });
|
||||
}
|
||||
|
||||
@@ -866,9 +858,7 @@ export class OverlayStackBox extends Box {
|
||||
let item = Layout.horizontal({
|
||||
layout: [
|
||||
this._addOverlayIcon(overlay),
|
||||
'<div style="background-color: rgba(0, 0, 0, 0.6); padding: 3px; border-radius: 3px; word-break: break-word;">' +
|
||||
name +
|
||||
"</div>",
|
||||
name,
|
||||
Layout.horizontal({ layout: optBtn }),
|
||||
],
|
||||
cssStyle: {
|
||||
@@ -923,6 +913,9 @@ export class OverlayStackBox extends Box {
|
||||
hipsOptions.sort()
|
||||
|
||||
for (const layer of layers) {
|
||||
if (!layer) {
|
||||
continue;
|
||||
}
|
||||
let options = Array.from([...hipsOptions])
|
||||
let value = layer.name || layer.id
|
||||
|
||||
@@ -930,6 +923,7 @@ export class OverlayStackBox extends Box {
|
||||
options.push(value)
|
||||
}
|
||||
|
||||
options.push("More...")
|
||||
|
||||
let HiPSSelector = Input.select({
|
||||
value,
|
||||
@@ -937,6 +931,14 @@ export class OverlayStackBox extends Box {
|
||||
title: layer.name,
|
||||
change: (e) => {
|
||||
let name = e.target.value;
|
||||
|
||||
if (name === "More...") {
|
||||
if (!self.hipsBrowser)
|
||||
self.hipsBrowser = new HiPSBrowserBox(self.aladin);
|
||||
|
||||
self.hipsBrowser._show({ layer: layer.layer, position: { anchor: "center center" } });
|
||||
return;
|
||||
}
|
||||
// search for the
|
||||
let overlayLayer;
|
||||
if (name in self.cachedHiPS) {
|
||||
@@ -954,8 +956,7 @@ export class OverlayStackBox extends Box {
|
||||
|
||||
let deleteBtn = ActionButton.createSmallSizedIconBtn({
|
||||
icon: { url: removeIconUrl, monochrome: true },
|
||||
|
||||
disable: layer.layer === "base",
|
||||
//disable: layer.layer === "base",
|
||||
tooltip: { content: "Remove", position: { direction: "top" } },
|
||||
action(e) {
|
||||
self.aladin.removeImageLayer(layer.layer);
|
||||
@@ -964,6 +965,7 @@ export class OverlayStackBox extends Box {
|
||||
},
|
||||
});
|
||||
|
||||
let prevOpacity = null;
|
||||
let showBtn = ActionButton.createSmallSizedIconBtn({
|
||||
icon: {
|
||||
url: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
|
||||
@@ -979,12 +981,15 @@ export class OverlayStackBox extends Box {
|
||||
|
||||
let opacity = layer.getOpacity();
|
||||
if (opacity === 0.0) {
|
||||
layer.setOpacity(1.0);
|
||||
let newOpacity = prevOpacity || 1.0;
|
||||
prevOpacity = null;
|
||||
layer.setOpacity(newOpacity);
|
||||
btn.update({
|
||||
icon: { monochrome: true, url: showIconUrl },
|
||||
tooltip: { content: "Hide" },
|
||||
});
|
||||
} else {
|
||||
prevOpacity = opacity;
|
||||
layer.setOpacity(0.0);
|
||||
btn.update({
|
||||
icon: { monochrome: true, url: hideIconUrl },
|
||||
@@ -1105,12 +1110,45 @@ export class OverlayStackBox extends Box {
|
||||
},
|
||||
});
|
||||
|
||||
self.layer2swap = null;
|
||||
let swapBtn = new ActionButton({
|
||||
size: "small",
|
||||
icon: {
|
||||
url: swapIcon,
|
||||
size: "small",
|
||||
monochrome: true,
|
||||
},
|
||||
tooltip: {
|
||||
content: "Swap 2 layers",
|
||||
position: { direction: "top" },
|
||||
},
|
||||
toggled: false,
|
||||
action: (_) => {
|
||||
let toggled = swapBtn.options.toggled;
|
||||
if (!toggled) {
|
||||
if (!self.layer2swap) {
|
||||
self.layer2swap = layer;
|
||||
} else {
|
||||
self.aladin.view.swapLayers(self.layer2swap.layer, layer.layer);
|
||||
}
|
||||
} else {
|
||||
if (self.layer2swap) {
|
||||
self.layer2swap = null;
|
||||
}
|
||||
}
|
||||
|
||||
swapBtn.update({
|
||||
toggled: !toggled,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let btns = [showBtn, settingsBtn];
|
||||
|
||||
if (!(layer instanceof Image)) {
|
||||
btns.push(loadMOCBtn);
|
||||
}
|
||||
btns.push(deleteBtn);
|
||||
btns = btns.concat([swapBtn, deleteBtn]);
|
||||
|
||||
let item = Layout.horizontal({
|
||||
layout: [HiPSSelector, Layout.horizontal(btns)],
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2023 - UDS/CNRS
|
||||
// The Aladin Lite program is distributed under the terms
|
||||
// of the GNU General Public License version 3.
|
||||
//
|
||||
// This file is part of Aladin Lite.
|
||||
//
|
||||
// Aladin Lite is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 3 of the License.
|
||||
//
|
||||
// Aladin Lite is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// The GNU General Public License is available in COPYING file
|
||||
// along with Aladin Lite.
|
||||
//
|
||||
|
||||
import { ALEvent } from "../../events/ALEvent";
|
||||
|
||||
import { ActionButton } from "../Widgets/ActionButton";
|
||||
import mapIconUrl from '../../../../assets/icons/map.svg';
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
* File gui/ActionButton.js
|
||||
*
|
||||
* A context menu that shows when the user right clicks, or long touch on touch device
|
||||
*
|
||||
*
|
||||
* Author: Matthieu Baumann[CDS]
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
export class MainSurveyActionButton extends ActionButton {
|
||||
/**
|
||||
* UI responsible for displaying the viewport infos
|
||||
* @param {Aladin} aladin - The aladin instance.
|
||||
*/
|
||||
constructor(aladin, options) {
|
||||
super({
|
||||
...options,
|
||||
tooltip: {content: 'Survey name<br/>Click to change it!', position: { direction: 'bottom' }},
|
||||
iconURL: mapIconUrl,
|
||||
})
|
||||
|
||||
this.addClass('medium-sized-icon')
|
||||
|
||||
this._addListeners(aladin)
|
||||
}
|
||||
|
||||
_addListeners(aladin) {
|
||||
ALEvent.HIPS_LAYER_ADDED.listenedBy(aladin.aladinDiv, (e) => {
|
||||
const layer = e.detail.layer;
|
||||
if (layer.layer === 'base') {
|
||||
let name = (layer.properties && layer.properties.obsTitle) || layer.name;
|
||||
this.update({
|
||||
tooltip: {
|
||||
content: 'Survey: ' + name,
|
||||
position: {
|
||||
direction: 'left'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,17 @@ export class SettingsCtxMenu extends ContextMenu {
|
||||
content: [self.backgroundColorInput, 'Back color']
|
||||
},
|
||||
},
|
||||
{
|
||||
label: {
|
||||
content: 'Light/Dark mode'
|
||||
},
|
||||
action(o) {
|
||||
const currentTheme = self.aladin.aladinDiv.getAttribute("data-theme");
|
||||
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
||||
self.aladin.aladinDiv.setAttribute("data-theme", newTheme);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: {
|
||||
content: [self.hpxGridCheckbox, 'HEALPix grid']
|
||||
|
||||
@@ -79,7 +79,7 @@ export class Dropdown extends Input {
|
||||
|
||||
super({
|
||||
type: 'text',
|
||||
autocomplete: {options: options.options},
|
||||
//autocomplete: {options: options.options},
|
||||
...options
|
||||
})
|
||||
this.el.classList.add('search')
|
||||
@@ -91,11 +91,11 @@ export class Dropdown extends Input {
|
||||
update(options) {
|
||||
let newOptions = {};
|
||||
|
||||
if (options && options.options) {
|
||||
/*if (options && options.options) {
|
||||
newOptions['autocomplete'] = {options: options.options};
|
||||
|
||||
delete options.options;
|
||||
}
|
||||
}*/
|
||||
|
||||
// add the other input text options
|
||||
newOptions = {...newOptions, ...options};
|
||||
|
||||
@@ -67,6 +67,47 @@ export class Layout extends DOMElement {
|
||||
this.appendContent(item)
|
||||
}
|
||||
}
|
||||
|
||||
if (options.draggable) {
|
||||
// retrieve the children and add the drag listeners
|
||||
let draggableFn = options.draggable;
|
||||
let firstSelected = null;
|
||||
|
||||
this.el.childNodes.forEach(div => {
|
||||
div.addEventListener("click", () => {
|
||||
// If nothing selected yet → select this one
|
||||
if (!firstSelected) {
|
||||
firstSelected = div;
|
||||
div.classList.add("aladin-item-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
// If clicking the same one again → unselect
|
||||
if (firstSelected === div) {
|
||||
div.classList.remove("aladin-item-selected");
|
||||
firstSelected = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise: swap the two elements
|
||||
let a = firstSelected;
|
||||
let b = div;
|
||||
|
||||
let temp = document.createElement("div");
|
||||
a.parentNode.insertBefore(temp, a);
|
||||
b.parentNode.insertBefore(a, b);
|
||||
temp.parentNode.insertBefore(b, temp);
|
||||
temp.remove();
|
||||
|
||||
// Exec callback
|
||||
draggableFn(a, b)
|
||||
|
||||
// Clear selection
|
||||
a.classList.remove("aladin-item-selected");
|
||||
firstSelected = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The tooltip has to be set once the element
|
||||
@@ -182,9 +223,5 @@ export class Layout extends DOMElement {
|
||||
if (this.options.position) {
|
||||
this.setPosition(this.options.position)
|
||||
}
|
||||
|
||||
//super._show()
|
||||
// attach to the DOM again
|
||||
//this.attachTo(this.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,9 @@ export class Location extends DOMElement {
|
||||
// lon and lat must be given in cooFrame
|
||||
const updateFromLonLatFunc = (lon, lat, cooFrame) => {
|
||||
var coo = new Coo(lon, lat, Location.prec);
|
||||
|
||||
cooFrame = CooFrameEnum.fromString(cooFrame);
|
||||
|
||||
if (cooFrame == CooFrameEnum.ICRS) {
|
||||
self.field.set(coo.format('s/'));
|
||||
}
|
||||
@@ -237,7 +240,7 @@ export class Location extends DOMElement {
|
||||
self.field.removeClass('aladin-not-valid');
|
||||
self.field.removeClass('aladin-valid');
|
||||
|
||||
self.field.element().style.color = options.center ? 'var(--aladin-color)' : 'white';
|
||||
self.field.element().style.color = options.center ? 'var(--aladin-color)' : 'var(--text-color)';
|
||||
};
|
||||
|
||||
if (options.ra && options.dec) {
|
||||
|
||||
@@ -137,11 +137,7 @@ export class Box extends DOMElement {
|
||||
|
||||
if (this.options.content) {
|
||||
let content = this.options.content
|
||||
//if (Array.isArray(content)) {
|
||||
this.appendContent(content);
|
||||
//} else {
|
||||
// this.appendContent(content);
|
||||
//}
|
||||
this.appendContent(content);
|
||||
}
|
||||
|
||||
if (this.options.position) {
|
||||
|
||||
@@ -74,12 +74,17 @@ export class Input extends DOMElement {
|
||||
this.el.type = this.type;
|
||||
|
||||
this.el.checked = this.options.checked;
|
||||
this.checked = this.options.checked;
|
||||
|
||||
// for checkbox widgets, we authorize calling the callback name click or change
|
||||
let action = this.options.click || this.options.change;
|
||||
if (action) {
|
||||
this.el.removeEventListener('click', this.action);
|
||||
this.action = action;
|
||||
this.action = (e) => {
|
||||
this.checked = this.el.checked;
|
||||
|
||||
action(e);
|
||||
};
|
||||
this.el.addEventListener('click', this.action);
|
||||
}
|
||||
} else if (this.type === "select") {
|
||||
@@ -374,6 +379,7 @@ export class Input extends DOMElement {
|
||||
set(value) {
|
||||
if (this.el.type === "checkbox") {
|
||||
this.el.checked = value;
|
||||
this.checked = value;
|
||||
} else {
|
||||
this.el.value = value;
|
||||
}
|
||||
|
||||
331
src/js/gui/Widgets/Tree.js
Normal file
@@ -0,0 +1,331 @@
|
||||
// Copyright 2023 - UDS/CNRS
|
||||
// The Aladin Lite program is distributed under the terms
|
||||
// of the GNU General Public License version 3.
|
||||
//
|
||||
// This file is part of Aladin Lite.
|
||||
//
|
||||
// Aladin Lite is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 3 of the License.
|
||||
//
|
||||
// Aladin Lite is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// The GNU General Public License is available in COPYING file
|
||||
// along with Aladin Lite.
|
||||
//
|
||||
|
||||
import { DOMElement } from "./Widget";
|
||||
import { Icon } from "./Icon";
|
||||
import folderIconUrl from "../../../../assets/icons/folder.svg";
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
* File gui/Tree.js
|
||||
*
|
||||
* A tree
|
||||
*
|
||||
* Author: Matthieu Baumann[CDS]
|
||||
*
|
||||
*****************************************************************************
|
||||
*/
|
||||
export class Tree extends DOMElement {
|
||||
constructor(options, target, position = "beforeend") {
|
||||
let el = document.createElement("div");
|
||||
el.classList.add('aladin-tree');
|
||||
|
||||
super(el, options);
|
||||
|
||||
this.click = options && options.click;
|
||||
|
||||
let rootNode = options && options.root || {};
|
||||
this.params = null;
|
||||
this.filter = options && options.filter;
|
||||
this.label = options && options.label;
|
||||
|
||||
this._setRoot(rootNode);
|
||||
|
||||
this.attachTo(target, position);
|
||||
this._show();
|
||||
this.addClass('aladin-dark-theme')
|
||||
}
|
||||
|
||||
_setRoot(root) {
|
||||
if (root) {
|
||||
root.label = new Icon({
|
||||
size: "small",
|
||||
url: folderIconUrl,
|
||||
monochrome: true,
|
||||
cssStyle: {'display': 'inline-block'}
|
||||
}).element().outerHTML;
|
||||
}
|
||||
|
||||
this.root = root;
|
||||
this._createDOM(root);
|
||||
}
|
||||
|
||||
_createDOM(node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.innerHTML = "";
|
||||
this.curNode = node;
|
||||
|
||||
// create the parent directory
|
||||
let curNode = this.curNode;
|
||||
|
||||
let directoryLinks = []
|
||||
|
||||
var levelsOfParenty = 0;
|
||||
|
||||
while (curNode) {
|
||||
let parentLinkEl = document.createElement('a');
|
||||
parentLinkEl.innerHTML = curNode.label;
|
||||
let curLevel = levelsOfParenty;
|
||||
parentLinkEl.addEventListener('click', () => {
|
||||
this.navigate(curLevel)
|
||||
})
|
||||
levelsOfParenty += 1;
|
||||
|
||||
parentLinkEl.classList.add("aladin-link");
|
||||
directoryLinks.push(parentLinkEl);
|
||||
|
||||
curNode = curNode.parent;
|
||||
}
|
||||
|
||||
directoryLinks.reverse()
|
||||
|
||||
let directoryListEl = document.createElement('div');
|
||||
directoryListEl.classList.add('aladin-directory-path');
|
||||
directoryListEl.style.display = "inline-block"
|
||||
|
||||
for (var link of directoryLinks) {
|
||||
directoryListEl.appendChild(link);
|
||||
|
||||
// root node
|
||||
let spanSplitEl = document.createElement('span')
|
||||
spanSplitEl.innerText = ' \/ '
|
||||
directoryListEl.appendChild(spanSplitEl)
|
||||
}
|
||||
|
||||
this.el.appendChild(directoryListEl)
|
||||
|
||||
let listElt = document.createElement('ul');
|
||||
|
||||
for (const label of Object.keys(node).sort()) {
|
||||
if (label !== 'parent' && label !== "label") {
|
||||
let elt = document.createElement('li');
|
||||
// points towards the parent node
|
||||
let child = node[label];
|
||||
let isLeaf = typeof child === "object" && 'ID' in child;
|
||||
if (isLeaf) {
|
||||
if(this.params && this.filter && !this.filter(child, this.params)) {
|
||||
elt.style.display = "none";
|
||||
} else {
|
||||
elt.style.display = "block";
|
||||
}
|
||||
|
||||
elt.innerHTML = this.label(child);
|
||||
} else {
|
||||
// we see a parent, we must determine:
|
||||
// * its color: he has at least 1 child inside the FoV => green
|
||||
// * the number of children matching the filter params
|
||||
let numFilteringMatching = this.numChildMatchingFilter(child, true);
|
||||
let numTotal = this.numChildMatchingFilter(child, false);
|
||||
|
||||
let name = label;
|
||||
elt.innerHTML = name + ` (${numFilteringMatching}/${numTotal})`
|
||||
|
||||
if (numFilteringMatching == 0) {
|
||||
elt.style.display = "none";
|
||||
} else {
|
||||
elt.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
elt.style.color = this.hasChildLocatedInFov(child) ? 'yellowgreen' : 'orange';
|
||||
|
||||
child.label = label;
|
||||
child.parent = node;
|
||||
|
||||
elt.for = label;
|
||||
elt.classList.add("aladin-link");
|
||||
|
||||
elt.addEventListener('click', (e) => {
|
||||
if (isLeaf) {
|
||||
this.click(child)
|
||||
} else {
|
||||
// not leaf
|
||||
this._createDOM(child);
|
||||
}
|
||||
})
|
||||
|
||||
listElt.appendChild(elt)
|
||||
}
|
||||
}
|
||||
|
||||
this.el.appendChild(listElt);
|
||||
}
|
||||
|
||||
setHierarchy(root) {
|
||||
this._setRoot(root)
|
||||
}
|
||||
|
||||
navigate(numOfLevels) {
|
||||
let curNode = this.curNode;
|
||||
while (curNode && curNode.parent && numOfLevels >= 1) {
|
||||
numOfLevels -= 1;
|
||||
curNode = curNode.parent;
|
||||
}
|
||||
|
||||
this._createDOM(curNode)
|
||||
}
|
||||
|
||||
highlightNodes(highlight) {
|
||||
this.highlight = highlight
|
||||
|
||||
let elts = this.el.querySelectorAll("li");
|
||||
let i = 0;
|
||||
|
||||
for (const label of Object.keys(this.curNode).sort()) {
|
||||
if (label !== 'parent' && label !== "label") {
|
||||
let elt = elts[i];
|
||||
i += 1;
|
||||
// points towards the parent node
|
||||
let child = this.curNode[label];
|
||||
let isLeaf = typeof child === "object" && 'ID' in child;
|
||||
if (isLeaf) {
|
||||
// Check if its ID is found in the view
|
||||
if (this.highlight) {
|
||||
elt.style.color = this.highlight.includes(child.ID) ? 'yellowgreen' : 'orange';
|
||||
}
|
||||
} else {
|
||||
// we see a parent, we must determine:
|
||||
// * its color: he has at least 1 child inside the FoV => green
|
||||
// * the number of children matching the filter params
|
||||
elt.style.color = this.hasChildLocatedInFov(child) ? 'yellowgreen' : 'orange';
|
||||
|
||||
// we see a parent, we must determine:
|
||||
// * its color: he has at least 1 child inside the FoV => green
|
||||
// * the number of children matching the filter params
|
||||
let numFilteringMatching = this.numChildMatchingFilter(child, true);
|
||||
let numTotal = this.numChildMatchingFilter(child, false);
|
||||
|
||||
let name = elt.innerText.split('(');
|
||||
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
|
||||
|
||||
if (numFilteringMatching == 0) {
|
||||
elt.style.display = "none";
|
||||
} else {
|
||||
elt.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set params to null, undefined or {} to disable the filtering
|
||||
triggerFilter(params) {
|
||||
if (params && params.title) {
|
||||
params.title = params.title.toLowerCase()
|
||||
}
|
||||
|
||||
this.params = params;
|
||||
|
||||
let elts = this.el.querySelectorAll("li");
|
||||
let i = 0;
|
||||
|
||||
for (const label of Object.keys(this.curNode).sort()) {
|
||||
if (label !== 'parent' && label !== "label") {
|
||||
let elt = elts[i];
|
||||
i += 1;
|
||||
// points towards the parent node
|
||||
let child = this.curNode[label];
|
||||
let isLeaf = typeof child === "object" && 'ID' in child;
|
||||
if (isLeaf) {
|
||||
if(this.params && this.filter && !this.filter(child, this.params)) {
|
||||
elt.style.display = "none"
|
||||
} else {
|
||||
elt.style.display = "block"
|
||||
}
|
||||
} else {
|
||||
// we see a parent, we must determine:
|
||||
// * its color: he has at least 1 child inside the FoV => green
|
||||
// * the number of children matching the filter params
|
||||
let numFilteringMatching = this.numChildMatchingFilter(child, true);
|
||||
let numTotal = this.numChildMatchingFilter(child, false);
|
||||
|
||||
let name = elt.innerText.split('(');
|
||||
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
|
||||
if (numFilteringMatching == 0) {
|
||||
elt.style.display = "none";
|
||||
} else {
|
||||
elt.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasChildLocatedInFov(node) {
|
||||
if (!this.highlight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof node !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let isLeaf = typeof node === "object" && 'ID' in node;
|
||||
if (isLeaf) {
|
||||
if (this.highlight.includes(node.ID)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (const label of Object.keys(node).sort()) {
|
||||
if (label === "parent")
|
||||
continue;
|
||||
|
||||
let child = node[label];
|
||||
if (child && this.hasChildLocatedInFov(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
numChildMatchingFilter(node, filtering) {
|
||||
if (typeof node !== "object") {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let isLeaf = typeof node === "object" && 'ID' in node;
|
||||
if (isLeaf) {
|
||||
if (!filtering || (this.params && this.filter && this.filter(node, this.params))) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
let num = 0;
|
||||
for (const label of Object.keys(node).sort()) {
|
||||
if (label === "parent")
|
||||
continue;
|
||||
|
||||
let child = node[label];
|
||||
|
||||
if (child) {
|
||||
num += this.numChildMatchingFilter(child, filtering);
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,28 @@ export class DOMElement {
|
||||
this.options = options;
|
||||
this.name = options && options.name || Utils.uuidv4()
|
||||
this.isHidden = true;
|
||||
|
||||
/*this.el.addEventListener("mouseup", (e) => {
|
||||
var wasDragging = view.realDragging === true;
|
||||
|
||||
if (view.dragging) { // if we were dragging, reset to default cursor
|
||||
if(view.mode === View.PAN) {
|
||||
view.setCursor('default');
|
||||
}
|
||||
|
||||
view.dragging = false;
|
||||
if (wasDragging) {
|
||||
view.realDragging = false;
|
||||
|
||||
// call the positionChanged once more with a dragging = false
|
||||
view.throttledPositionChanged(false);
|
||||
}
|
||||
|
||||
if (view.spectraDisplayer) {
|
||||
view.spectraDisplayer.enableInteraction();
|
||||
}
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
element() {
|
||||
|
||||
@@ -270,7 +270,7 @@ export let Ellipse = (function() {
|
||||
if (! baseColor) {
|
||||
baseColor = '#ff0000';
|
||||
}
|
||||
|
||||
|
||||
if (this.isSelected) {
|
||||
if(this.selectionColor) {
|
||||
ctx.strokeStyle = this.selectionColor;
|
||||
@@ -318,8 +318,6 @@ export let Ellipse = (function() {
|
||||
let [xb, yb] = getVertexOnEllipse(3 * Math.PI * 0.5)
|
||||
let [xc, yc] = getVertexOnEllipse(Math.PI)
|
||||
let [xd, yd] = getVertexOnEllipse(0)
|
||||
ctx.save();
|
||||
|
||||
ctx.lineWidth = Math.max(this.lineWidth * 0.5, 1.0);
|
||||
ctx.setLineDash([this.lineWidth, this.lineWidth]);
|
||||
|
||||
@@ -329,8 +327,6 @@ export let Ellipse = (function() {
|
||||
ctx.lineTo(xd, yd);
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,16 +61,9 @@ export let Polyline = (function() {
|
||||
return mag2;
|
||||
}
|
||||
|
||||
function _drawLine(l, ctx, noStroke) {
|
||||
noStroke = noStroke===true || false;
|
||||
|
||||
ctx.beginPath();
|
||||
function _drawLine(l, ctx) {
|
||||
ctx.moveTo(l.x1, l.y1);
|
||||
ctx.lineTo(l.x2, l.y2);
|
||||
|
||||
if (!noStroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +225,6 @@ export let Polyline = (function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
noSmallCheck = noSmallCheck===true || false;
|
||||
noStroke = noStroke===true || false;
|
||||
|
||||
@@ -270,8 +262,6 @@ export let Polyline = (function() {
|
||||
let ymin = Number.POSITIVE_INFINITY
|
||||
let ymax = Number.NEGATIVE_INFINITY;
|
||||
|
||||
|
||||
|
||||
let behind = true;
|
||||
for (var k=0; k<len; k++) {
|
||||
var xyview = view.aladin.world2pix(this.raDecArray[k][0], this.raDecArray[k][1]);
|
||||
@@ -380,10 +370,12 @@ export let Polyline = (function() {
|
||||
let v0 = this.closed ? len - 1 : 0;
|
||||
let v1 = this.closed ? 0 : 1;
|
||||
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.beginPath();
|
||||
|
||||
for (var k = 0; k < nSegment; k++) {
|
||||
|
||||
drawLine(xyView[v0], xyView[v1]);
|
||||
|
||||
v0 = v1;
|
||||
@@ -408,17 +400,15 @@ export let Polyline = (function() {
|
||||
v1 = v1 + 1;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = this.fillColor;
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Polyline.prototype.isInStroke = function(ctx, view, x, y) {
|
||||
ctx.beginPath()
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
|
||||
let pointXY = [];
|
||||
@@ -441,8 +431,8 @@ export let Polyline = (function() {
|
||||
|
||||
if (v1 && v2) {
|
||||
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
|
||||
_drawLine(line, ctx, true);
|
||||
|
||||
_drawLine(line, ctx);
|
||||
|
||||
if (ctx.isPointInStroke(x, y)) { // x, y is on line?
|
||||
return true;
|
||||
}
|
||||
@@ -455,7 +445,7 @@ export let Polyline = (function() {
|
||||
|
||||
if (v1 && v2) {
|
||||
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
|
||||
_drawLine(line, ctx, true);
|
||||
_drawLine(line, ctx);
|
||||
|
||||
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
|
||||
return true;
|
||||
|
||||
@@ -80,7 +80,7 @@ export class SAMPConnector {
|
||||
let params = message["samp.params"];
|
||||
|
||||
const {url, name} = params;
|
||||
const image = aladin.createImageFITS(url, {name}, (e) => window.alert(e));
|
||||
const image = aladin.createImageFITS(url, {name}, undefined, (e) => window.alert(e));
|
||||
|
||||
aladin.setOverlayImageLayer(image, name);
|
||||
};
|
||||
@@ -92,6 +92,8 @@ export class SAMPConnector {
|
||||
let url = params['url'];
|
||||
let name = params['name'] || id;
|
||||
|
||||
console.log(id, url, name)
|
||||
|
||||
A.catalogFromURL(
|
||||
url,
|
||||
{name, onClick: 'showTable'},
|
||||
@@ -99,7 +101,7 @@ export class SAMPConnector {
|
||||
(catalog) => {
|
||||
aladin.addCatalog(catalog)
|
||||
},
|
||||
(e) => window.alert(e)
|
||||
(e) => window.alert(e),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 133 KiB |
103
vite.config.ts
@@ -7,56 +7,61 @@ import wasm from "vite-plugin-wasm";
|
||||
import topLevelAwait from "vite-plugin-top-level-await";
|
||||
// For wasm generated by wasm-pack
|
||||
import wasmPack from 'vite-plugin-wasm-pack';
|
||||
// To include and minify glsl into the bundle
|
||||
import glsl from 'vite-plugin-glsl';
|
||||
// To include css into the bundle
|
||||
//import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
minify: 'esbuild',
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points
|
||||
entry: resolve(__dirname, 'src/js/A.js'),
|
||||
name: 'A',
|
||||
formats: ["umd", "es"],
|
||||
// the proper extensions will be added
|
||||
fileName: 'aladin',
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
|
||||
export default defineConfig(({mode}) => {
|
||||
return {
|
||||
build: {
|
||||
minify: 'esbuild',
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points
|
||||
entry: resolve(__dirname, 'src/js/A.js'),
|
||||
name: 'A',
|
||||
formats: ["umd", "es"],
|
||||
// the proper extensions will be added
|
||||
fileName: 'aladin',
|
||||
},
|
||||
rollupOptions: {},
|
||||
//formats: ["es"],
|
||||
target: ["es2015", "chrome58", "edge18", "firefox57", "node12", "safari11"],
|
||||
//target: ["es2015"],
|
||||
// Relative to the root
|
||||
outDir: resolve(__dirname, 'dist'),
|
||||
},
|
||||
rollupOptions: {},
|
||||
//formats: ["es"],
|
||||
target: ["es2015", "chrome58", "edge18", "firefox57", "node12", "safari11"],
|
||||
//target: ["es2015"],
|
||||
// Relative to the root
|
||||
outDir: resolve(__dirname, 'dist'),
|
||||
},
|
||||
//publicDir: resolve(__dirname, 'src/img'),
|
||||
plugins: [
|
||||
wasm(),
|
||||
wasmPack(resolve(__dirname, 'src/core')),
|
||||
topLevelAwait(),
|
||||
glsl({
|
||||
compress: true,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: [
|
||||
{find: '@', replacement: path.resolve(__dirname, '/src')},
|
||||
{find: '#', replacement: path.resolve(__dirname, '/tests/unit')},
|
||||
{find: '$', replacement: path.resolve(__dirname, '/tests/e2e')}
|
||||
//publicDir: resolve(__dirname, 'src/img'),
|
||||
plugins: [
|
||||
wasm(),
|
||||
wasmPack(resolve(__dirname, 'src/core')),
|
||||
topLevelAwait(),
|
||||
mode === 'analyze' &&
|
||||
visualizer({
|
||||
filename: 'stats.html', // output file
|
||||
template: 'treemap', // "sunburst", "treemap", "network"
|
||||
gzipSize: true, // show gzip sizes
|
||||
brotliSize: true, // show brotli sizes
|
||||
open: true // open stats.html automatically
|
||||
}),
|
||||
],
|
||||
},
|
||||
//test: {
|
||||
// globals: true,
|
||||
// environment: 'happy-dom',
|
||||
// include: [
|
||||
// 'tests/unit/**/*.{test,spec}.{js,ts}'
|
||||
// ],
|
||||
// deps: {
|
||||
// inline: ['core/pkg'],
|
||||
// },
|
||||
//},
|
||||
server: {
|
||||
open: '/examples/index.html',
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{find: '@', replacement: path.resolve(__dirname, '/src')},
|
||||
{find: '#', replacement: path.resolve(__dirname, '/tests/unit')},
|
||||
{find: '$', replacement: path.resolve(__dirname, '/tests/e2e')}
|
||||
],
|
||||
},
|
||||
//test: {
|
||||
// globals: true,
|
||||
// environment: 'happy-dom',
|
||||
// include: [
|
||||
// 'tests/unit/**/*.{test,spec}.{js,ts}'
|
||||
// ],
|
||||
// deps: {
|
||||
// inline: ['core/pkg'],
|
||||
// },
|
||||
//},
|
||||
server: {
|
||||
open: '/examples/index.html',
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||