Compare commits

..

22 Commits

Author SHA1 Message Date
Matthieu Baumann
6fd09ce10e make the HiPS browser more robust and implement a very proto interface for querying the HiPS on the fly generation from cube service: https://alasky.cds.unistra.fr/onthefly-cube-hips/ 2024-05-16 10:27:27 +02:00
Matthieu Baumann
51ee3c0516 multiple fixes for accessing SODA SKA services, HiPS browser, etc... 2024-05-06 15:16:30 +02:00
Matthieu Baumann
fa1bd8353a ui fixes and WIP browser 2024-05-06 15:14:48 +02:00
Matthieu Baumann
1d00b58351 fix: select options remove multiple spaces 2024-05-06 15:14:48 +02:00
Matthieu Baumann
a0fc0306e7 first commit 2024-05-06 15:14:46 +02:00
Matthieu Baumann
2981e634d2 fix: call aladin methods that calls to wasm in the positionChanged callback 2024-05-06 15:12:44 +02:00
Matthieu Baumann
5e8d0aca42 rename ImageSurvey in ImageHiPS, add a global cache for HiPS and FITS 2024-04-30 09:59:08 +02:00
Matthieu Baumann
9e0caa54c2 Add selector in the UI for HiPS/FITS images 2024-04-29 15:52:04 +02:00
Matthieu Baumann
5c4f60d4fd 3.4.0-beta 2024-04-25 09:27:26 +02:00
Matthieu Baumann
669fb26114 fix gotoobject giving 0 ra or 0 dec 2024-04-25 09:21:13 +02:00
Matthieu Baumann
d655d8f8bd remove view.idx zoom 2024-04-25 08:52:53 +02:00
Matthieu Baumann
027a76f2ab add tooltip on hips layers 2024-04-24 22:04:41 +02:00
Matthieu Baumann
d765dc9ec2 Add basic HiPS filter in the stack box 2024-04-23 22:25:37 +02:00
Matthieu Baumann
63aebf738a new UI StackBox 2024-04-23 00:19:45 +02:00
Matthieu Baumann
116ba0d2e3 save the imageHiPS once it has been changed so that the HiPS stack is saved. One can go back from the UI afterwards 2024-04-23 00:19:45 +02:00
Matthieu Baumann
34460eb9e7 add zoom heuristic for mouse 2024-04-23 00:19:45 +02:00
Matthieu Baumann
1d68495f57 update changelog 2024-04-23 00:19:45 +02:00
Matthieu Baumann
fee97bed40 For Catalog only: plot the sources when the footprints are too small 2024-04-23 00:19:45 +02:00
Matthieu Baumann
c797aec7f7 add doc for the different shapes 2024-04-23 00:19:45 +02:00
Matthieu Baumann
b68358f6b2 add proper motion example 2024-04-23 00:19:45 +02:00
Matthieu Baumann
9109c69fc3 expose Line in the API and add an example with proper motions drawn from a Simbad CS around the LMC 2024-04-23 00:19:45 +02:00
Matthieu Baumann
d56dbd1659 first commit 2024-04-23 00:19:45 +02:00
62 changed files with 4888 additions and 3854 deletions

View File

@@ -2,6 +2,7 @@
## 3.3.3
* [feat] UI: add HiPS basic filter that filters the `hipsList` given
* [feat] New `hipsList` option parameter when instancing a new Aladin object.
* [feat] Zoom smoothing using hermite cubic interpolation functions
* [feat] shape option of Catalog and ProgressiveCat accepts a function returning a Footprint. This allow user to

View File

@@ -25,6 +25,7 @@ Always prefer using the latest version. If you want the new features without min
## API documentation
There is a new in progress API documentation at [this link](https://cds-astro.github.io/aladin-lite).
Editable examples showing the API can also be found [here](https://aladin.cds.unistra.fr/AladinLite/doc/API/examples/).
## Embed it into your projects

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 15L21 21M21 15L15 21M10 21V14.6627C10 14.4182 10 14.2959 9.97237 14.1808C9.94787 14.0787 9.90747 13.9812 9.85264 13.8917C9.7908 13.7908 9.70432 13.7043 9.53137 13.5314L3.46863 7.46863C3.29568 7.29568 3.2092 7.2092 3.14736 7.10828C3.09253 7.01881 3.05213 6.92127 3.02763 6.81923C3 6.70414 3 6.58185 3 6.33726V4.6C3 4.03995 3 3.75992 3.10899 3.54601C3.20487 3.35785 3.35785 3.20487 3.54601 3.10899C3.75992 3 4.03995 3 4.6 3H19.4C19.9601 3 20.2401 3 20.454 3.10899C20.6422 3.20487 20.7951 3.35785 20.891 3.54601C21 3.75992 21 4.03995 21 4.6V6.33726C21 6.58185 21 6.70414 20.9724 6.81923C20.9479 6.92127 20.9075 7.01881 20.8526 7.10828C20.7908 7.2092 20.7043 7.29568 20.5314 7.46863L17 11" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1012 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4.6C3 4.03995 3 3.75992 3.10899 3.54601C3.20487 3.35785 3.35785 3.20487 3.54601 3.10899C3.75992 3 4.03995 3 4.6 3H19.4C19.9601 3 20.2401 3 20.454 3.10899C20.6422 3.20487 20.7951 3.35785 20.891 3.54601C21 3.75992 21 4.03995 21 4.6V6.33726C21 6.58185 21 6.70414 20.9724 6.81923C20.9479 6.92127 20.9075 7.01881 20.8526 7.10828C20.7908 7.2092 20.7043 7.29568 20.5314 7.46863L14.4686 13.5314C14.2957 13.7043 14.2092 13.7908 14.1474 13.8917C14.0925 13.9812 14.0521 14.0787 14.0276 14.1808C14 14.2959 14 14.4182 14 14.6627V17L10 21V14.6627C10 14.4182 10 14.2959 9.97237 14.1808C9.94787 14.0787 9.90747 13.9812 9.85264 13.8917C9.7908 13.7908 9.70432 13.7043 9.53137 13.5314L3.46863 7.46863C3.29568 7.29568 3.2092 7.2092 3.14736 7.10828C3.09253 7.01881 3.05213 6.92127 3.02763 6.81923C3 6.70414 3 6.58185 3 6.33726V4.6Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

@@ -17,6 +17,7 @@
</div>
<script type="module">
import A from '../src/js/A.js';
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', cooFrame: 'galactic', fov: 360, fullScreen: true, showCooGrid: false, showReticle: false})
aladin.gotoRaDec(79.9525321, -69.2742586)
@@ -50,8 +51,7 @@
}
}
aladin.setCooGrid({opacity: 0, color: {r: 51/255, g: 209/255, b: 1}})
aladin.setCooGrid({enabled: true})
aladin.setCooGrid({enabled: true, opacity: 0, color: {r: 51, g: 209, b: 255}})
async function s_1() {
return new Promise((resolve, reject) => {

View File

@@ -23,9 +23,7 @@
});
let pmraMean = null, pmdecMean = null;
//aladin.addCatalog(A.catalogFromSimbad('LMC', 2.5, {
//aladin.addCatalog(A.catalogFromVizieR('J/A+A/663/A107/table5', 'LMC', 5, {
const pmCat = A.catalogFromURL('./data/proper_motion.xml', {
onClick: 'showTable',
name: 'mean pm over HPX cells around LMC from GaiaDR2',

View File

@@ -1,36 +1,42 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<head> </head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from "../src/js/A.js";
let aladin;
A.init.then(() => {
aladin = A.aladin("#aladin-lite-div", {
target: "12 25 41.512 +12 48 47.2",
inertia: false,
fov: 1,
showContextMenu: true,
});
// define custom draw function
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {target: '12 25 41.512 +12 48 47.2', inertia: false, fov: 1, showContextMenu: true});
// define custom draw function
var hips = A.catalogHiPS(
"https://axel.u-strasbg.fr/HiPSCatService/Simbad",
{
onClick: "showTable",
name: "Simbad",
color: "cyan",
hoverColor: "red",
shape: (s) => {
let a = +s.data.size_maj;
let b = +s.data.size_min;
var drawFunctionFootprint = function(s) {
let a = +s.data.size_maj;
let b = +s.data.size_min;
let galaxy = ["Seyfert","Gin","StarburstG","LINER","AGN","Galaxy"].some((n) => s.data.main_type.indexOf(n) >= 0);
if (!galaxy) return;
let galaxy = ['Seyfert', 'Gin', 'StarburstG', 'LINER', 'AGN', 'Galaxy'].some((n) => s.data.main_type.indexOf(n) >= 0)
if (!galaxy)
return;
let theta = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta, {color: 'cyan'});
};
var hips = A.catalogHiPS('https://axel.u-strasbg.fr/HiPSCatService/Simbad', {onClick: 'showTable', name: 'Simbad', color: 'cyan', hoverColor: 'red', shape: drawFunctionFootprint});
aladin.addCatalog(hips);
});
</script>
</body>
let theta = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta, { color: "cyan" });
},
}
);
aladin.addCatalog(hips);
});
</script>
</body>
</html>

View File

@@ -26,7 +26,6 @@
}, // no optional params
(ra, dec, fov, image) => {
// ra, dec and fov are centered around the fits image
console.log("jjj", image)
image.setColormap("magma", {stretch: "asinh"});
aladin.gotoRaDec(ra, dec);

View File

@@ -11,7 +11,7 @@
import A from '../src/js/A.js';
A.init.then(() => {
var aladin = A.aladin('#aladin-lite-div', {target: '05 37 58 +08 17 35', fov: 12, backgroundColor: 'rgb(120, 0, 0)'});
var cat = A.catalog({sourceSize: 20});
var cat = A.catalog({sourceSize: 20, onClick: 'showTable'});
aladin.addCatalog(cat);
cat.addSources([A.source(83.784490, 9.934156, {name: 'Meissa'}), A.source(88.792939, 7.407064, {name: 'Betelgeuse'}), A.source(81.282764, 6.349703, {name: 'Bellatrix'})]);
var msg;
@@ -51,6 +51,8 @@
}
$('#infoDiv').html(msg);
});
cat.sources[0].actionClicked();
});
</script>
</body>

View File

@@ -24,7 +24,7 @@
showCooGrid: false, // set the grid
fullScreen: true,
inertia: false,
showStatusBar: false,
showStatusBar: true,
showShareControl: true,
showSettingsControl: true,
showLayersControl: true,

View File

@@ -16,7 +16,7 @@
fov: 360,
target: '0 0',
fullScreen: true,
survey: ["fsdfsd", "jfjfj", "jfj", "P/allWISE/color"],
survey: ["azef", "jfjfj", "jfj", "P/allWISE/color"],
showCooGrid: true,
});
});

88
examples/al-onthefly.html Normal file
View File

@@ -0,0 +1,88 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {
fullScreen: true,
target: "ngc 1436",
fov: 2,
projection: 'TAN',
showContextMenu: true,
showSimbadPointerControl: true,
expandLayersControl: true,
hipsList: [
// High energy (Gamma and X-rays)
'CDS/P/HGPS/Flux',
'CDS/P/Fermi/5',
'CDS/P/Fermi/4',
'CDS/P/Fermi/3',
'ov-gso/P/Fermi/Band2',
'ov-gso/P/BAT/150-195keV',
'ov-gso/P/BAT/35-50keV',
'ov-gso/P/BAT/14-20keV',
'erosita/dr1/rate/023',
'erosita/dr1/rate/024',
// Uv/Optical/Infrared
'CDS/P/GALEXGR6_7/FUV',
'CDS/P/GALEXGR6_7/NUV',
'CDS/P/DSS2/color',
'CDS/P/PanSTARRS/DR1/g',
'CDS/P/PanSTARRS/DR1/r',
'CDS/P/Finkbeiner',
'CDS/P/PanSTARRS/DR1/i',
'CDS/P/PanSTARRS/DR1/color-i-r-g',
'CDS/P/PanSTARRS/DR1/z',
'CDS/P/PanSTARRS/DR1/y',
'CDS/P/DES-DR2/ColorIRG',
'CDS/P/2MASS/color',
'ov-gso/P/GLIMPSE/irac1',
'ov-gso/P/GLIMPSE/irac2',
'CDS/P/unWISE/color-W2-W1W2-W1',
'ov-gso/P/GLIMPSE/irac3',
'ov-gso/P/GLIMPSE/irac4',
'CDS/P/IRIS/color',
'ESAVO/P/AKARI/N60',
'ESAVO/P/AKARI/WideL',
'ESAVO/P/HERSCHEL/SPIRE-250',
'ESAVO/P/HERSCHEL/SPIRE-350',
'ESAVO/P/HERSCHEL/SPIRE-500',
// sub-mm/mm/radio
'CDS/P/PLANCK/R3/HFI/color',
'CDS/P/ACT_Planck/DR5/f220',
'CDS/P/CO',
'CDS/P/PLANCK/R3/HFI100',
'CDS/P/PLANCK/R3/LFI30',
'CDS/P/NVSS',
'CSIRO/P/RACS/mid/I',
'ov-gso/P/CGPS/VGPS',
'CDS/C/HI4PI/HI',
'CDS/P/MeerKAT/Galactic-Centre-1284MHz-StokesI',
'CSIRO/P/RACS/low/I',
'astron.nl/P/tgssadr',
'ov-gso/P/GLEAM/170-231',
'ov-gso/P/GLEAM/139-170',
'astron.nl/P/lotss_dr2_high',
'ov-gso/P/GLEAM/103-134',
'ov-gso/P/GLEAM/072-103'
]
});
aladin.addCatalog(
A.catalogFromSKAORucio("ngc 1436", 15, {
onClick: 'showTable',
hoverColor: "yellow",
})
);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,95 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {
fullScreen: true,
target: "centaurus A",
fov: 10,
projection: 'TAN',
showContextMenu: true,
showSimbadPointerControl: true,
expandLayersControl: true,
hipsList: [
// High energy (Gamma and X-rays)
'CDS/P/HGPS/Flux',
'CDS/P/Fermi/5',
'CDS/P/Fermi/4',
'CDS/P/Fermi/3',
'ov-gso/P/Fermi/Band2',
'ov-gso/P/BAT/150-195keV',
'ov-gso/P/BAT/35-50keV',
'ov-gso/P/BAT/14-20keV',
'erosita/dr1/rate/023',
'erosita/dr1/rate/024',
// Uv/Optical/Infrared
'CDS/P/GALEXGR6_7/FUV',
'CDS/P/GALEXGR6_7/NUV',
'CDS/P/DSS2/color',
'CDS/P/PanSTARRS/DR1/g',
'CDS/P/PanSTARRS/DR1/r',
'CDS/P/Finkbeiner',
'CDS/P/PanSTARRS/DR1/i',
'CDS/P/PanSTARRS/DR1/color-i-r-g',
'CDS/P/PanSTARRS/DR1/z',
'CDS/P/PanSTARRS/DR1/y',
'CDS/P/DES-DR2/ColorIRG',
'CDS/P/2MASS/color',
'ov-gso/P/GLIMPSE/irac1',
'ov-gso/P/GLIMPSE/irac2',
'CDS/P/unWISE/color-W2-W1W2-W1',
'ov-gso/P/GLIMPSE/irac3',
'ov-gso/P/GLIMPSE/irac4',
'CDS/P/IRIS/color',
'ESAVO/P/AKARI/N60',
'ESAVO/P/AKARI/WideL',
'ESAVO/P/HERSCHEL/SPIRE-250',
'ESAVO/P/HERSCHEL/SPIRE-350',
'ESAVO/P/HERSCHEL/SPIRE-500',
// sub-mm/mm/radio
'CDS/P/PLANCK/R3/HFI/color',
'CDS/P/ACT_Planck/DR5/f220',
'CDS/P/CO',
'CDS/P/PLANCK/R3/HFI100',
'CDS/P/PLANCK/R3/LFI30',
'CDS/P/NVSS',
'CSIRO/P/RACS/mid/I',
'ov-gso/P/CGPS/VGPS',
'CDS/C/HI4PI/HI',
'CDS/P/MeerKAT/Galactic-Centre-1284MHz-StokesI',
'CSIRO/P/RACS/low/I',
'astron.nl/P/tgssadr',
'ov-gso/P/GLEAM/170-231',
'ov-gso/P/GLEAM/139-170',
'astron.nl/P/lotss_dr2_high',
'ov-gso/P/GLEAM/103-134',
'ov-gso/P/GLEAM/072-103'
]
});
const gleam072_103 = aladin.newImageSurvey('ov-gso/P/GLEAM/072-103', {imgFormat: 'fits', colormap: "red", stretch: "asinh"});
const gleam103_134 = aladin.newImageSurvey('ov-gso/P/GLEAM/103-134', {imgFormat: 'fits', colormap: "green", additive: true, stretch: "asinh"});
const gleam170_231 = aladin.newImageSurvey('ov-gso/P/GLEAM/170-231', {imgFormat: 'fits', colormap: "blue", additive: true, stretch: "asinh"});
//const HSCBlueSurvey = aladin.newImageSurvey('CDS/P/HSC/DR2/deep/z', {imgFormat: 'fits', colormap: "blue", minCut: -0.01218, maxCut: 2.27397, additive: true, stretch: "asinh"});
aladin.setBaseImageLayer(gleam072_103);
aladin.setOverlayImageLayer(gleam103_134, 'gl2');
aladin.setOverlayImageLayer(gleam170_231, 'gl3');
//aladin.setOverlayImageLayer('CDS/P/HSC/DR2/deep/z', 'hsc blue layer');
//const c1 = A.catalogFromURL('./data/eso.xml', {onClick: 'showTable'});
//aladin.addCatalog(c1);
});
</script>
</body>
</html>

View File

@@ -1,33 +1,88 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<head> </head>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, target: "Abell 194", fov: 180, projection: 'AIT', showContextMenu: true});
aladin.setImageSurvey('astron.nl/P/lotss_dr2_high')
A.catalogFromSKAORucio("Abell 194", 90, {onClick: 'showTable', hoverColor: 'yellow'}, (cat) => {
aladin.addCatalog(cat);
});
<script type="module">
import A from "../src/js/A.js";
let aladin;
A.init.then(() => {
aladin = A.aladin("#aladin-lite-div", {
fullScreen: true,
target: "m51",
fov: 15,
projection: "AIT",
showContextMenu: true,
showShareControl: true,
hipsList: [
// High energy (Gamma and X-rays)
"CDS/P/HGPS/Flux",
"CDS/P/Fermi/5",
"CDS/P/Fermi/4",
"CDS/P/Fermi/3",
"ov-gso/P/Fermi/Band2",
"ov-gso/P/BAT/150-195keV",
"ov-gso/P/BAT/35-50keV",
"ov-gso/P/BAT/14-20keV",
"erosita/dr1/rate/023",
"erosita/dr1/rate/024",
// Uv/Optical/Infrared
"CDS/P/GALEXGR6_7/FUV",
"CDS/P/GALEXGR6_7/NUV",
"CDS/P/DSS2/color",
"CDS/P/PanSTARRS/DR1/g",
"CDS/P/PanSTARRS/DR1/r",
"CDS/P/Finkbeiner",
"CDS/P/PanSTARRS/DR1/i",
"CDS/P/PanSTARRS/DR1/color-i-r-g",
"CDS/P/PanSTARRS/DR1/z",
"CDS/P/PanSTARRS/DR1/y",
"CDS/P/DES-DR2/ColorIRG",
"CDS/P/2MASS/color",
"ov-gso/P/GLIMPSE/irac1",
"ov-gso/P/GLIMPSE/irac2",
"CDS/P/unWISE/color-W2-W1W2-W1",
"ov-gso/P/GLIMPSE/irac3",
"ov-gso/P/GLIMPSE/irac4",
"CDS/P/IRIS/color",
"ESAVO/P/AKARI/N60",
"ESAVO/P/AKARI/WideL",
"ESAVO/P/HERSCHEL/SPIRE-250",
"ESAVO/P/HERSCHEL/SPIRE-350",
"ESAVO/P/HERSCHEL/SPIRE-500",
// sub-mm/mm/radio
"CDS/P/PLANCK/R3/HFI/color",
"CDS/P/ACT_Planck/DR5/f220",
"CDS/P/CO",
"CDS/P/PLANCK/R3/HFI100",
"CDS/P/PLANCK/R3/LFI30",
"CDS/P/NVSS",
"CSIRO/P/RACS/mid/I",
"ov-gso/P/CGPS/VGPS",
"CDS/C/HI4PI/HI",
"CDS/P/MeerKAT/Galactic-Centre-1284MHz-StokesI",
"CSIRO/P/RACS/low/I",
"astron.nl/P/tgssadr",
"ov-gso/P/GLEAM/170-231",
"ov-gso/P/GLEAM/139-170",
"astron.nl/P/lotss_dr2_high",
"ov-gso/P/GLEAM/103-134",
"ov-gso/P/GLEAM/072-103",
],
});
/*A.catalogFromURL('https://aladin.cds.unistra.fr/AladinLite/ska/obscoreRucio.xml', {onClick: 'showTable'}, (cat) => {
aladin.addCatalog(cat);
});
aladin.setImageLayer(A.imageHiPS("P/DSS2/color"));
A.catalogFromURL('data/votable/CDS-B-jcmt-obscore.xml', {onClick: 'showTable'}, (cat) => {
aladin.addCatalog(cat);
});*/
A.catalogFromURL('https://aladin.cds.unistra.fr/ObsCoreRucioScs.xml', {onClick: 'showTable'}, (cat) => {
aladin.addCatalog(cat);
});
});
</script>
</body>
aladin.addCatalog(
A.catalogFromSKAORucio("m51", 15, {
onClick: 'showTable',
hoverColor: "yellow",
})
);
//aladin.addCatalog(A.catalogFromURL('https://aladin.cds.unistra.fr/ObsCoreRucioScs.xml', {onClick: 'showTable'}));
});
</script>
</body>
</html>

View File

@@ -14,17 +14,16 @@
aladin = A.aladin('#aladin-lite-div', {target: "30 0", fov: 360, fullScreen: true, cooFrame: 'galactic', showCooGridControl: true, showSimbadPointerControl: true, showCooGrid: true});
aladin.setProjection('AIT');
aladin.on("zoomChanged", () => {
console.log("zoomChanged")
})
aladin.on("positionChanged", ({ra, dec, dragging}) => {
console.log("positionChanged", ra, dec)
aladin.on("positionChanged", ({ra, dec}) => {
console.log('call to aladin', aladin.pix2world(300, 300))
console.log('positionChanged in icrs', ra, dec)
})
aladin.gotoRaDec(0, 20);
aladin.on('rightClickMove', (x, y) => {
console.log("right click move", x, y)
})

View File

@@ -2,7 +2,7 @@
"homepage": "https://aladin.u-strasbg.fr/",
"name": "aladin-lite",
"type": "module",
"version": "3.3.3",
"version": "3.4.1-beta",
"description": "An astronomical HiPS visualizer in the browser",
"author": "Thomas Boch and Matthieu Baumann",
"license": "GPL-3",

View File

@@ -3,7 +3,7 @@ name = "aladin-lite"
description = "Aladin Lite v3 introduces a new graphical engine written in Rust with the use of WebGL"
license = "BSD-3-Clause"
repository = "https://github.com/cds-astro/aladin-lite"
version = "3.3.2"
version = "3.4.0-beta"
authors = [ "baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr",]
edition = "2018"

View File

@@ -44,7 +44,9 @@ impl ImageFormat for RGB8U {
fn decode(raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
let mut decoder = jpeg::Decoder::new(raw_bytes);
let bytes = decoder.decode().map_err(|_| "Cannot decoder jpeg. This image may not be compressed.")?;
let bytes = decoder
.decode()
.map_err(|_| "Cannot decoder jpeg. This image may not be compressed.")?;
Ok(Bytes::Owned(bytes))
}
@@ -70,7 +72,9 @@ impl ImageFormat for RGBA8U {
fn decode(raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
let mut decoder = jpeg::Decoder::new(raw_bytes);
let bytes = decoder.decode().map_err(|_| "Cannot decoder png. This image may not be compressed.")?;
let bytes = decoder
.decode()
.map_err(|_| "Cannot decoder png. This image may not be compressed.")?;
Ok(Bytes::Owned(bytes))
}
@@ -93,7 +97,9 @@ impl ImageFormat for RGBA8U {
fn decode(raw_bytes: &[u8]) -> Result<Bytes<'_>, &'static str> {
let mut decoder = jpeg::Decoder::new(raw_bytes);
let bytes = decoder.decode().map_err(|_| "Cannot decoder png. This image may not be compressed.")?;
let bytes = decoder
.decode()
.map_err(|_| "Cannot decoder png. This image may not be compressed.")?;
Ok(Bytes::Owned(bytes))
}
@@ -188,7 +194,6 @@ impl ImageFormat for R32F {
}
}
#[cfg(feature = "webgl2")]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct R64F;
@@ -310,6 +315,8 @@ pub enum ChannelType {
R32I,
}
pub const NUM_CHANNELS: usize = 9;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct ImageFormatType {
pub ext: ImageExt,
@@ -327,8 +334,11 @@ impl ImageFormatType {
pub fn is_colored(&self) -> bool {
match self.channel {
ChannelType::RGBA32F | ChannelType::RGB32F | ChannelType::RGBA8U | ChannelType::RGB8U => true,
_ => false
ChannelType::RGBA32F
| ChannelType::RGB32F
| ChannelType::RGBA8U
| ChannelType::RGB8U => true,
_ => false,
}
}
}

View File

@@ -106,9 +106,8 @@ pub struct App {
ack_send: async_channel::Sender<ImageParams>,
ack_recv: async_channel::Receiver<ImageParams>,
// callbacks
callback_position_changed: js_sys::Function,
//callback_position_changed: js_sys::Function,
}
use cgmath::{Vector2, Vector3};
@@ -131,7 +130,7 @@ impl App {
mut shaders: ShaderManager,
resources: Resources,
// Callbacks
callback_position_changed: js_sys::Function,
//callback_position_changed: js_sys::Function,
) -> Result<Self, JsValue> {
let gl = gl.clone();
//let exec = Rc::new(RefCell::new(TaskExecutor::new()));
@@ -261,8 +260,7 @@ impl App {
fits_recv,
ack_send,
ack_recv,
callback_position_changed,
//callback_position_changed,
})
}
@@ -540,8 +538,12 @@ impl App {
Ok(())
}
pub(crate) fn set_callback_position_changed(&mut self, callback: js_sys::Function) {
/*pub(crate) fn set_callback_position_changed(&mut self, callback: js_sys::Function) {
self.callback_position_changed = callback;
}*/
pub(crate) fn is_inerting(&self) -> bool {
return self.inertia.is_some();
}
pub(crate) fn update(&mut self, _dt: DeltaTime) -> Result<bool, JsValue> {
@@ -560,7 +562,7 @@ impl App {
let cur_speed = inertia.get_cur_speed();
// Create the javascript object to pass to the callback
let args: js_sys::Object = js_sys::Object::new();
/*let args: js_sys::Object = js_sys::Object::new();
let center = self.camera.get_center().lonlat();
js_sys::Reflect::set(
&args,
@@ -578,6 +580,7 @@ impl App {
// Position has changed, we call the callback
self.callback_position_changed
.call1(&JsValue::null(), &args)?;
*/
if cur_speed < thresh_speed {
self.inertia = None;
@@ -1024,10 +1027,10 @@ impl App {
Ok(())
}
pub(crate) fn add_image_survey(&mut self, hips_cfg: HiPSCfg) -> Result<(), JsValue> {
pub(crate) fn add_image_hips(&mut self, hips_cfg: HiPSCfg) -> Result<(), JsValue> {
let hips =
self.layers
.add_image_survey(&self.gl, hips_cfg, &mut self.camera, &self.projection)?;
.add_image_hips(&self.gl, hips_cfg, &mut self.camera, &self.projection)?;
self.tile_fetcher
.launch_starting_hips_requests(hips, &mut self.downloader);

View File

@@ -166,13 +166,10 @@ impl WebClient {
let shaders = ShaderManager::new(&gl, shaders).unwrap_abort();
// Event listeners callbacks
let callback_position_changed = js_sys::Function::new_no_args("");
//let callback_position_changed = js_sys::Function::new_no_args("");
let app = App::new(
&gl,
aladin_div,
shaders,
resources,
callback_position_changed,
&gl, aladin_div, shaders, resources,
//callback_position_changed,
)?;
let dt = DeltaTime::zero();
@@ -182,9 +179,14 @@ impl WebClient {
Ok(webclient)
}
#[wasm_bindgen(js_name = setCallbackPositionChanged)]
/*#[wasm_bindgen(js_name = setCallbackPositionChanged)]
pub fn set_callback_position_changed(&mut self, callback: js_sys::Function) {
self.app.set_callback_position_changed(callback);
}*/
#[wasm_bindgen(js_name = isInerting)]
pub fn is_inerting(&self) -> bool {
return self.app.is_inerting();
}
/// Update the view
@@ -364,11 +366,11 @@ impl WebClient {
/// * If the number of surveys is greater than 4. For the moment, due to the limitations
/// of WebGL2 texture units on some architectures, the total number of surveys rendered is
/// limited to 4.
#[wasm_bindgen(js_name = addImageSurvey)]
pub fn add_image_survey(&mut self, hips: JsValue) -> Result<(), JsValue> {
#[wasm_bindgen(js_name = addImageHiPS)]
pub fn add_image_hips(&mut self, hips: JsValue) -> Result<(), JsValue> {
// Deserialize the survey objects that compose the survey
let hips = serde_wasm_bindgen::from_value(hips)?;
self.app.add_image_survey(hips)?;
self.app.add_image_hips(hips)?;
Ok(())
}

View File

@@ -12,6 +12,7 @@ use al_core::image::Image;
use al_core::log::console_log;
use al_core::shader::Shader;
use al_core::webgl_ctx::GlWrapper;
use al_core::Texture2DArray;
use al_core::VecData;
use al_core::VertexArrayObject;
use al_core::WebGlContext;
@@ -43,6 +44,7 @@ use uv::{TileCorner, TileUVW};
use cgmath::{Matrix, Matrix4};
use std::fmt::Debug;
use std::rc::Rc;
use wasm_bindgen::JsValue;
use web_sys::WebGl2RenderingContext;
@@ -381,11 +383,7 @@ pub struct HiPS {
}
impl HiPS {
pub fn new(
config: HiPSConfig,
gl: &WebGlContext,
_camera: &CameraViewPort,
) -> Result<Self, JsValue> {
pub fn new(config: HiPSConfig, gl: &WebGlContext) -> Result<Self, JsValue> {
let mut vao = VertexArrayObject::new(gl);
// layout (location = 0) in vec2 lonlat;
@@ -497,8 +495,6 @@ impl HiPS {
.unbind();
let num_idx = 0;
//let min_depth_tile = config.get_min_depth_tile();
let textures = ImageSurveyTextures::new(gl, config)?;
let gl = gl.clone();

View File

@@ -10,6 +10,7 @@ pub mod utils;
use crate::renderable::image::Image;
use al_core::image::format::ChannelType;
use al_core::Texture2DArray;
pub use hips::HiPS;
pub use catalog::Manager;
@@ -20,6 +21,7 @@ use al_api::hips::ImageMetadata;
use al_api::image::ImageParams;
use al_core::colormap::Colormaps;
use al_core::image::format::NUM_CHANNELS;
use al_core::shader::Shader;
use al_core::SliceData;
use al_core::VertexArrayObject;
@@ -38,6 +40,7 @@ use hips::raytracing::RayTracer;
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use wasm_bindgen::JsValue;
use web_sys::WebGl2RenderingContext;
@@ -421,7 +424,7 @@ impl Layers {
Ok(())
}
pub fn add_image_survey(
pub fn add_image_hips(
&mut self,
gl: &WebGlContext,
hips: HiPSCfg,
@@ -481,7 +484,7 @@ impl Layers {
}*/
camera.register_view_frame(cfg.get_frame(), proj);
let hips = HiPS::new(cfg, gl, camera)?;
let hips = HiPS::new(cfg, gl)?;
// add the frame to the camera
self.surveys.insert(creator_did.clone(), hips);
@@ -613,16 +616,6 @@ impl Layers {
Ok(())
}
/*pub fn is_ready(&self) -> bool {
let ready = self
.surveys
.iter()
.map(|(_, survey)| survey.is_ready())
.fold(true, |acc, x| acc & x);
ready
}*/
// Accessors
// HiPSes getters
pub fn get_hips_from_layer(&self, layer: &str) -> Option<&HiPS> {

View File

@@ -138,7 +138,7 @@ pub struct ImageSurveyTextures {
//pub cutoff_values_tile: Rc<RefCell<HashMap<HEALPixCell, (f32, f32)>>>,
// Array of 2D textures
texture_2d_array: Texture2DArray,
pub texture_2d_array: Texture2DArray,
// A boolean ensuring the root textures
// have already been loaded
@@ -168,11 +168,7 @@ fn create_texture_array<F: ImageFormat>(
}
impl ImageSurveyTextures {
pub fn new(
gl: &WebGlContext,
config: HiPSConfig,
//exec: Rc<RefCell<TaskExecutor>>,
) -> Result<ImageSurveyTextures, JsValue> {
pub fn new(gl: &WebGlContext, config: HiPSConfig) -> Result<ImageSurveyTextures, JsValue> {
let size = config.num_textures();
// Ensures there is at least space for the 12
// root textures
@@ -212,7 +208,6 @@ impl ImageSurveyTextures {
#[cfg(feature = "webgl2")]
ChannelType::R64F => create_texture_array::<R64F>(gl, &config)?,
};
// The root textures have not been loaded
//let ready = false;
//let num_root_textures_available = 0;

View File

@@ -1,4 +1,5 @@
use al_api::hips::ImageExt;
use al_core::log::console_log;
use al_core::{image::format::ImageFormat, image::raw::ImageBuffer};
#[derive(Debug)]

View File

@@ -5,6 +5,8 @@
/* disable x swipe on chrome, firefox */
/* see. https://stackoverflow.com/questions/30636930/disable-web-page-navigation-on-swipeback-and-forward */
overscroll-behavior-x: none;
/* Hide the draggable boxes that goes out of the view */
overflow: hidden;
/* media query on the aladin lite container. not supported everywhere.
There can be a more supported alternative here: https://caniuse.com/?search=grid-template-columns */
/*container-type: inline-size;*/
@@ -94,56 +96,30 @@
.aladin-measurement-div.aladin-dark-theme table thead {
background-color: #000;
color: white;
}
.aladin-measurement-div table td.aladin-href-td-container a:hover {
overflow: visible;
display: inline-block;
animation: leftright 5s infinite normal linear;
}
@keyframes leftright {
0%,
20% {
transform: translateX(0%);
left: 0%;
}
80%,
100% {
/* the max width is 150px and a padding of 0.8em is added for href link */
transform: translateX(-100%);
left: -100%;
}
}
.aladin-measurement-div table th {
padding: 0.3em 0.5em;
}
.aladin-measurement-div table td.aladin-href-td-container {
border: 1px solid #d2d2d2;
.aladin-measurement-div table tr td a {
display: block;
}
border-radius: 3px;
.aladin-measurement-div table tr td {
padding: 0.5rem;
}
.aladin-measurement-div table tr td, .aladin-measurement-div table tr td a {
max-width: 10rem;
text-overflow: ellipsis;
padding: 0.5em;
white-space: nowrap;
overflow: hidden;
text-align: center;
/*max-width: 150px;
text-overflow: ellipsis;*/
}
.aladin-measurement-div table td.aladin-text-td-container {
padding: 0.5em;
white-space: nowrap;
overflow: hidden;
/*max-width: 150px;
text-overflow: ellipsis;*/
}
.aladin-measurement-div table td.aladin-href-td-container:hover {
background-color: #fff;
word-wrap:break-word;
}
.aladin-marker-measurement {
@@ -159,10 +135,6 @@
width: 100%;
}
.aladin-marker-measurement table tr td{
word-wrap:break-word;
}
.aladin-marker-measurement tr:nth-child(even) {
background-color: #dddddd;
}
@@ -353,16 +325,15 @@ canvas {
.aladin-btn.aladin-dark-theme.toggled {
border-color: greenyellow;
}
.aladin-btn.aladin-dark-theme:hover, .aladin-input-select.aladin-dark-theme:hover {
border-color: red;
}
.aladin-btn.disabled {
cursor: not-allowed;
filter: brightness(70%);
}
.aladin-btn:not(.disabled):hover {
filter: brightness(105%);
}
.aladin-btn.svg-icon {
background-repeat: no-repeat;
background-position:center center;
@@ -373,6 +344,13 @@ canvas {
display: flex;
justify-content: center;
align-items: center;
}
.aladin-icon img {
vertical-align:middle;
width: 100%;
height: 100%;
}
.aladin-icon.aladin-dark-theme {
@@ -417,29 +395,21 @@ canvas {
height: 1.7rem;
}
.aladin-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
.aladin-input-text.aladin-dark-theme:focus, .aladin-input-number.aladin-dark-theme:focus {
border-color: dodgerblue;
}
.aladin-input-text.aladin-dark-theme.search {
width: 15rem;
text-shadow: 0px 0px 2px #000;
}
.aladin-input-text.aladin-dark-theme.search:focus, .aladin-input-text.aladin-dark-theme.search:hover {
.aladin-input-text.search:focus, .aladin-input-text.search:hover {
background-image: url(../../assets/icons/search-white.svg);
background-size: 1.8rem;
background-repeat: no-repeat;
text-indent: 1.8rem;
padding-left: 1.8rem;
}
.aladin-input-text.search {
background-image:none;
text-indent: 0rem;
@@ -447,14 +417,20 @@ canvas {
line-height: 1.2rem;
}
.aladin-input-text.search.aladin-unknownObject {
.aladin-input-text.search.aladin-not-valid {
-webkit-box-shadow:inset 0px 0px 0px 1px #f00;
-moz-box-shadow:inset 0px 0px 0px 1px #f00;
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-dark-theme.aladin-valid {
border: 1px solid yellowgreen;
}
.aladin-cancelBtn {
background-color: #ca4242;
border-color: #bd3935;
@@ -490,12 +466,12 @@ canvas {
align-items: flex-end;
}
.aladin-vertical-list > *:first-of-type {
.aladin-vertical-list > *:first-child {
margin-top: 0;
}
.aladin-vertical-list > * {
margin-top: 0.2rem;
margin-top: 0.5rem;
}
.aladin-horizontal-list {
@@ -509,11 +485,14 @@ canvas {
margin-right: 0.2rem;
}
.aladin-horizontal-list > *::last-of-type {
vertical-align: middle;
.aladin-horizontal-list > *:last-child {
margin-right: 0;
}
.aladin-form {
width: 100%;
}
.aladin-form .aladin-form-input {
display: flex;
justify-content: flex-end;
@@ -521,8 +500,17 @@ canvas {
margin-bottom: 0.4rem;
}
.aladin-form .aladin-form-input:last-of-type {
margin-bottom: 0;
}
.aladin-form .aladin-form-group {
margin-bottom: 1rem;
width: 100%;
}
.aladin-form .aladin-form-group:last-of-type {
margin-bottom: 0rem;
}
.aladin-form .aladin-form-input select {
@@ -897,12 +885,17 @@ canvas {
/********** Range Input Styles **********/
/*Range Reset*/
.aladin-input-range {
-webkit-appearance: none;
appearance: none;
background: transparent;
cursor: pointer;
width: 5em;
height: 1.5rem;
background: transparent;
cursor: pointer;
width: 5em;
height: 0.1rem;
margin:0;
margin: 1rem 0;
}
.aladin-input-range.aladin-reversed {
direction: rtl;
}
/* Removes default focus */
@@ -911,47 +904,43 @@ canvas {
}
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
/* slider track */
.aladin-input-range::-webkit-slider-runnable-track {
background-color: #bababa;
border-radius: 0.1rem;
height: 0.1rem;
.aladin-input-range::-webkit-slider-container {
background: white;
height: 0.1rem;
min-height: 0.1rem;
}
/* slider thumb */
.aladin-input-range::-webkit-slider-thumb {
-webkit-appearance: none; /* Override default look */
/*
.aladin-input-range-datalist {
-webkit-appearance: none;
appearance: none;
margin-top: -7px; /* Centers thumb on the track */
/*custom styles*/
background-color: #bababa;
height: 1rem;
width: 1rem;
display: none;
display: flex;
position: absolute;
width: 100%;
padding:0;
margin:0;
height: 0.1rem;
top: 0rem;
pointer-events: none;
}
border-radius: 0.5rem;
}
/******** Firefox styles ********/
/* slider track */
.aladin-input-range::-moz-range-track {
background-color: #bababa;
.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;
}
/* slider thumb */
.aladin-input-range::-moz-range-thumb {
border: none; /*Removes extra border that FF applies*/
border-radius: 0; /*Removes default border-radius that FF applies*/
/*custom styles*/
background-color: #bababa;
height: 1rem;
width: 1rem;
border-radius: 0.5rem;
}
height: 0.1rem;
padding: 0;
margin: 0;
background: #D3D3D3;
}*/
.aladin-dark-theme {
color: white;
@@ -1107,17 +1096,27 @@ canvas {
height: 1.7rem;
}
/*
.aladin-input-text.aladin-dark-theme.search.aladin-HiPS-search {
width: 100%;
}
}*/
.aladin-stack-box {
width: 17rem;
}
.aladin-stack-box .content > * {
margin-bottom: 0.5rem;
.aladin-HiPS-filter-box {
border: 1px solid white;
}
.aladin-HiPS-browser-box .aladin-input-text {
width: 300px;
padding: 0.5rem;
}
.aladin-stack-box .aladin-input-select {
width: 100%;
}
.aladin-location {
@@ -1133,6 +1132,10 @@ canvas {
height: 1.7rem;
}
.aladin-location .aladin-input-text {
width: 15rem;
}
.aladin-fov {
position: absolute;
bottom: 0.2rem;

View File

@@ -106,13 +106,13 @@ A.aladin = function (divSelector, options) {
* @function
* @name A.imageHiPS
* @memberof A
* @param {string} id - Mandatory unique identifier for the survey.
* @param {string} url - Can be an `url` that refers to a HiPS.
* @param {string} id - Can be either an `url` that refers to a HiPS.
* Or it can be a "CDS ID" pointing towards a HiPS. One can found the list of IDs {@link https://aladin.cds.unistra.fr/hips/list| here}.
* @param {ImageSurveyOptions} [options] - Options describing the survey
* @returns {ImageSurvey} - A HiPS image object
* @param {ImageHiPSOptions} [options] - Options describing the survey
* @returns {ImageHiPS} - A HiPS image object
*/
A.imageHiPS = function (id, url, options) {
A.imageHiPS = function (id, options) {
let url = id;
return Aladin.createImageSurvey(
id,
options && options.name,
@@ -131,7 +131,7 @@ A.imageHiPS = function (id, url, options) {
* @memberof A
* @param {string} url - Options describing the fits file. An url is mandatory
* @param {ImageFITSOptions} [options] - Options describing the fits file. An url is mandatory
* @returns {ImageSurvey} - A HiPS image object
* @returns {ImageFITS} - A HiPS image object
* @example
* const sourceObj = A.source(180.0, 30.0, data, options);
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

252
src/js/DefaultHiPSCache.js Normal file
View File

@@ -0,0 +1,252 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File ImageHiPS
*
* Authors: Thomas Boch & Matthieu Baumann [CDS]
*
*****************************************************************************/
import { ALEvent } from "./events/ALEvent.js";
export let HiPSCache = (function () {
function HiPSCache() {}
HiPSCache.append = function (key, image) {
HiPSCache.cache[key] = image;
ALEvent.HIPS_CACHE_UPDATED.dispatchedTo(document.body);
};
HiPSCache.delete = function (key) {
delete HiPSCache.cache[key];
ALEvent.HIPS_CACHE_UPDATED.dispatchedTo(document.body);
};
HiPSCache.get = function (key) {
return HiPSCache.cache[key];
};
// A cache storing directly surveys important information to not query for the properties each time
HiPSCache.cache = {};
HiPSCache.DEFAULT_HIPS_LIST = [
{
creatorDid: "ivo://CDS/P/DSS2/color",
name: "DSS colored",
id: "CDS/P/DSS2/color",
maxOrder: 9,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "ICRS",
},
{
creatorDid: "ivo://CDS/P/2MASS/color",
name: "2MASS colored",
id: "CDS/P/2MASS/color",
maxOrder: 9,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "ICRS",
},
{
creatorDid: "ivo://CDS/P/DSS2/red",
name: "DSS2 Red (F+R)",
id: "CDS/P/DSS2/red",
maxOrder: 9,
tileSize: 512,
imgFormat: "fits",
cooFrame: "ICRS",
numBitsPerPixel: 16,
// options
minCut: 1000.0,
maxCut: 10000.0,
colormap: "magma",
stretch: "Linear",
imgFormat: "fits",
},
{
creatorDid: "ivo://CDS/P/DM/I/350/gaiaedr3",
name: "Density map for Gaia EDR3 (I/350/gaiaedr3)",
id: "CDS/P/DM/I/350/gaiaedr3",
maxOrder: 7,
tileSize: 512,
numBitsPerPixel: -32,
cooFrame: "ICRS",
minCut: 0,
maxCut: 12000,
stretch: "asinh",
colormap: "rdylbu",
imgFormat: "fits",
},
{
creatorDid: "ivo://CDS/P/PanSTARRS/DR1/g",
name: "PanSTARRS DR1 g",
id: "CDS/P/PanSTARRS/DR1/g",
maxOrder: 11,
tileSize: 512,
imgFormat: "fits",
cooFrame: "ICRS",
numBitsPerPixel: -32,
// options
minCut: -34,
maxCut: 7000,
stretch: "asinh",
colormap: "redtemperature",
},
{
creatorDid: "ivo://CDS/P/PanSTARRS/DR1/color-z-zg-g",
name: "PanSTARRS DR1 color",
id: "CDS/P/PanSTARRS/DR1/color-z-zg-g",
maxOrder: 11,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "ICRS",
},
{
creatorDid: "ivo://CDS/P/DECaPS/DR2/color",
name: "DECaPS DR2 color",
id: "CDS/P/DECaPS/DR2/color",
maxOrder: 11,
cooFrame: "equatorial",
tileSize: 512,
imgFormat: "png",
},
{
creatorDid: "ivo://CDS/P/Fermi/color",
name: "Fermi color",
id: "CDS/P/Fermi/color",
maxOrder: 3,
imgFormat: "jpeg",
tileSize: 512,
cooFrame: "equatorial",
},
{
creatorDid: "ivo://CDS/P/GALEXGR6_7/NUV",
id: "P/GALEXGR6_7/NUV",
name: "GALEXGR6_7 NUV",
maxOrder: 8,
imgFormat: "png",
tileSize: 512,
cooFrame: "equatorial",
},
{
creatorDid: "ivo://CDS/P/IRIS/color",
id: "CDS/P/IRIS/color",
name: "IRIS colored",
maxOrder: 3,
tileSize: 256,
imgFormat: "jpeg",
cooFrame: "galactic",
},
{
creatorDid: "ivo://CDS/P/Mellinger/color",
id: "CDS/P/Mellinger/color",
name: "Mellinger colored",
maxOrder: 4,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "galactic",
},
{
creatorDid: "ivo://CDS/P/SDSS9/color",
id: "CDS/P/SDSS9/color",
name: "SDSS9 colored",
maxOrder: 10,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "equatorial",
},
{
creatorDid: "ivo://CDS/P/SPITZER/color",
id: "CDS/P/SPITZER/color",
name: "IRAC color I1,I2,I4 - (GLIMPSE, SAGE, SAGE-SMC, SINGS)",
maxOrder: 9,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "galactic",
},
{
creatorDid: "ivo://CDS/P/allWISE/color",
id: "CDS/P/allWISE/color",
name: "AllWISE color",
maxOrder: 8,
tileSize: 512,
imgFormat: "jpeg",
cooFrame: "equatorial",
},
{
creatorDid: "ivo://CDS/P/SDSS9/g",
id: "CDS/P/SDSS9/g",
name: "SDSS9 band-g",
maxOrder: 10,
tileSize: 512,
numBitsPerPixel: 16,
imgFormat: "fits",
cooFrame: "equatorial",
minCut: 0,
maxCut: 1.8,
stretch: "linear",
colormap: "redtemperature",
},
{
id: "CDS/P/Finkbeiner",
name: "Halpha",
maxOrder: 3,
minCut: -10,
maxCut: 800,
colormap: "rdbu",
imgFormat: "fits",
},
{
id: "CDS/P/VTSS/Ha",
name: "VTSS-Ha",
maxOrder: 3,
minCut: -10.0,
maxCut: 100.0,
colormap: "grayscale",
imgFormat: "fits",
},
{
id: "xcatdb/P/XMM/PN/color",
name: "XMM PN colored",
maxOrder: 7,
},
{
id: "CDS/P/allWISE/color",
name: "AllWISE color",
maxOrder: 8,
},
/*{
id: "CDS/P/GLIMPSE360",
name: "GLIMPSE360",
// This domain is not giving the CORS headers
// We need to query by with a proxy equipped with CORS header.
//url: "https://alasky.cds.unistra.fr/cgi/JSONProxy?url=https://www.spitzer.caltech.edu/glimpse360/aladin/data",
maxOrder: 9,
imgFormat: "jpeg",
minOrder: 3,
}*/
];
return HiPSCache;
})();

View File

@@ -1,130 +0,0 @@
import A from "./A.js";
export let DiscoveryTree = (function () {
// Constructor
var DiscoveryTree = function (aladin) {
// activate Vue on the <div> that contains the component
new Vue({
el: '#ui',
methods: {
// Define the methods for the discovery-tree component
// to interact with the aladin viewer
getFovCorners() {
return aladin.getFovCorners();
},
getCenter() {
return aladin.getRaDec();
},
// Called when the user add a image survey
addImage(metadata) {
const order = (+metadata.hips_order);
const hipsTileFormat = metadata.hips_tile_format.split(' ');
let tileFormat;
let color;
if (hipsTileFormat.indexOf('fits') >= 0) {
tileFormat = {
FITSImage: {
bitpix: parseInt(metadata.hips_pixel_bitpix)
}
};
color = {
Grayscale2Color: {
color: [1.0, 1.0, 1.0],
k: 1.0,
transfer: "asinh"
}
};
} else {
color = "Color";
if (hipsTileFormat.indexOf('png') >= 0) {
tileFormat = {
Image: {
format: "png"
}
};
} else {
tileFormat = {
Image: {
format: "jpeg"
}
};
}
}
let cuts = [undefined, undefined];
if (metadata.hips_pixel_cut) {
cuts = metadata.hips_pixel_cut.split(" ");
}
let tileSize = 512;
// Verify the validity of the tile width
if (metadata.hips_tile_width) {
let hipsTileWidth = parseInt(metadata.hips_tile_width);
let isPowerOfTwo = hipsTileWidth && !(hipsTileWidth & (hipsTileWidth - 1));
if (isPowerOfTwo === true) {
tileSize = hipsTileWidth;
}
}
let url = metadata.hips_service_url;
if (url.startsWith('http://alasky')) {
// From alasky one can directly use the https access
url = url.replace('http', 'https');
} else {
// Pass by a proxy for extern http urls
url = 'https://alasky.u-strasbg.fr/cgi/JSONProxy?url=' + url;
}
let survey = {
properties: {
url: url,
maxOrder: parseInt(metadata.hips_order),
frame: {
label: "J2000",
system: "J2000"
},
tileSize: tileSize,
format: tileFormat,
minCutout: parseFloat(cuts[0]),
maxCutout: parseFloat(cuts[1]),
},
color: color
};
aladin.setImageSurveysLayer([survey], "base");
},
// Called when the user add a catalog survey
addCatalog(metadata, center, radius) {
if (metadata.hips_service_url) {
const hips = A.catalogHiPS(metadata.hips_service_url, {
onClick: 'showTable',
name: metadata.ID,
});
aladin.addCatalog(hips);
} else {
console.log(metadata.obs_id, "center, ", center, " radius, ", radius)
const catalog = A.catalogFromVizieR(
metadata.obs_id,
{
ra: center[0],
dec: center[1]
},
radius, {
onClick: 'showTable',
limit: 1000,
}
);
aladin.addCatalog(catalog);
}
},
// Called when the user add a HEALPix coverage
addCoverage(metadata) {
const moc = A.MOCFromURL(metadata.moc_access_url);
aladin.addMOC(moc);
},
},
});
}
return DiscoveryTree;
})();

View File

@@ -59,7 +59,7 @@ HiPSProperties.fetchFromID = async function(ID) {
result = matching;
} else {
result = metadata[0];
console.warn("Multiple surveys are matching, please choose one. The chosen one is: " + result);
console.warn("Multiple surveys are matching, please choose one. The chosen one is: " + result.ID);
}
} else {
// Exactly one matching

View File

@@ -17,38 +17,63 @@
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
*
* File ImageFITS
*
*
* Authors: Matthieu Baumann [CDS]
*
*
*****************************************************************************/
import { ALEvent } from "./events/ALEvent.js";
import { ColorCfg } from "./ColorCfg.js";
import { Utils } from "./Utils";
import { HiPSCache } from "./DefaultHiPSCache";
/**
* @typedef {Object} ImageFITSOptions
*
* @property {string} [name] - A human-readable name for the FITS image
* @property {Function} [successCallback] - A callback executed when the FITS has been loaded
* @property {Function} [errorCallback] - A callback executed when the FITS could not be loaded
* @property {number} [opacity=1.0] - Opacity of the survey or image (value between 0 and 1).
* @property {string} [colormap="native"] - The colormap configuration for the survey or image.
* @property {string} [stretch="linear"] - The stretch configuration for the survey or image.
* @property {boolean} [reversed=false] - If true, the colormap is reversed; otherwise, it is not reversed.
* @property {number} [minCut] - The minimum cut value for the color configuration. If not given, 0.0 for JPEG/PNG surveys, the value of the property file for FITS surveys
* @property {number} [maxCut] - The maximum cut value for the color configuration. If not given, 1.0 for JPEG/PNG surveys, the value of the property file for FITS surveys
* @property {boolean} [additive=false] - If true, additive blending is applied; otherwise, it is not applied.
* @property {number} [gamma=1.0] - The gamma correction value for the color configuration.
* @property {number} [saturation=0.0] - The saturation value for the color configuration.
* @property {number} [brightness=0.0] - The brightness value for the color configuration.
* @property {number} [contrast=0.0] - The contrast value for the color configuration.
*/
export let ImageFITS = (function () {
function ImageFITS(url, name, options, successCallback = undefined, errorCallback = undefined) {
/**
* The object describing a FITS image
*
* @class
* @constructs ImageFITS
*
* @param {string} url - Mandatory unique identifier for the layer. Can be an arbitrary name
* @param {ImageFITSOptions} [options] - The option for the survey
*
*/
function ImageFITS(url, options) {
// Name of the layer
this.layer = null;
this.added = false;
this.subtype = "fits";
// Set it to a default value
this.url = url.toString();
this.id = url.toString();
this.name = name || this.url;
this.url = url;
this.id = url;
this.name = (options && options.name) || this.url;
this.imgFormat = "fits";
this.formats = ["fits"]
this.formats = ["fits"];
// callbacks
this.successCallback = successCallback;
this.errorCallback = errorCallback;
this.successCallback = options && options.successCallback;
this.errorCallback = options && options.errorCallback;
// initialize the color meta data here
// set a asinh stretch by default if there is none
/*if (options) {
@@ -60,13 +85,19 @@ export let ImageFITS = (function () {
this.query = Promise.resolve(self);
}
// A cache storing directly the images to not query for the properties each time
ImageFITS.cache = {};
ImageFITS.prototype.setView = function(view) {
ImageFITS.prototype._saveInCache = function () {
HiPSCache.append(this.id, this);
};
// A cache storing directly the images to not query for the properties each time
//ImageFITS.cache = {};
ImageFITS.prototype.setView = function (view) {
this.view = view;
}
this._saveInCache();
};
// @api
ImageFITS.prototype.setOpacity = function (opacity) {
@@ -88,7 +119,7 @@ export let ImageFITS = (function () {
this._updateMetadata(() => {
this.colorCfg.setColormap(colormap, options);
});
}
};
// @api
ImageFITS.prototype.setCuts = function (lowCut, highCut) {
@@ -135,92 +166,101 @@ export let ImageFITS = (function () {
this.view.wasm.setImageMetadata(this.layer, {
...this.colorCfg.get(),
longitudeReversed: false,
imgFormat: this.imgFormat
imgFormat: this.imgFormat,
});
ALEvent.HIPS_LAYER_CHANGED.dispatchedTo(this.view.aladinDiv, {
layer: this,
});
ALEvent.HIPS_LAYER_CHANGED.dispatchedTo(this.view.aladinDiv, { layer: this });
}
// save it in the JS HiPS cache
this._saveInCache();
} catch (e) {
// Display the error message
console.error(e);
}
}
};
ImageFITS.prototype.add = function (layer) {
this.layer = layer;
let self = this;
const promise = self.view.wasm.addImageFITS({
layer: self.layer,
url: self.url,
meta: {
...this.colorCfg.get(),
longitudeReversed: false,
imgFormat: this.imgFormat
}
}).then((imagesParams) => {
// There is at least one entry in imageParams
self.added = true;
const promise = self.view.wasm
.addImageFITS({
layer: self.layer,
url: self.url,
meta: {
...this.colorCfg.get(),
longitudeReversed: false,
imgFormat: this.imgFormat,
},
})
.then((imagesParams) => {
// There is at least one entry in imageParams
self.added = true;
self.children = [];
self.children = [];
let hduIdx = 0;
imagesParams.forEach((imageParams) => {
// This fits has HDU extensions
let image = new ImageFITS(
imageParams.url,
self.name + "_ext_" + hduIdx.toString(),
null,
null,
null
);
let hduIdx = 0;
imagesParams.forEach((imageParams) => {
// This fits has HDU extensions
let image = new ImageFITS(imageParams.url, {
name: self.name + "_ext_" + hduIdx.toString(),
});
// Set the layer corresponding to the onein the backend
image.layer = imageParams.layer;
image.added = true;
image.setView(self.view);
// deep copy of the color object of self
image.colorCfg = Utils.deepCopy(self.colorCfg);
// Set the automatic computed cuts
image.setCuts(imageParams.automatic_min_cut, imageParams.automatic_max_cut);
// Set the layer corresponding to the onein the backend
image.layer = imageParams.layer;
image.added = true;
image.setView(self.view);
// deep copy of the color object of self
image.colorCfg = Utils.deepCopy(self.colorCfg);
// Set the automatic computed cuts
image.setCuts(
imageParams.automatic_min_cut,
imageParams.automatic_max_cut
);
image.ra = imageParams.centered_fov.ra;
image.dec = imageParams.centered_fov.dec;
image.fov = imageParams.centered_fov.fov;
image.ra = imageParams.centered_fov.ra;
image.dec = imageParams.centered_fov.dec;
image.fov = imageParams.centered_fov.fov;
if (!self.ra) { self.ra = image.ra; }
if (!self.dec) { self.dec = image.dec; }
if (!self.fov) { self.fov = image.fov; }
if (!self.ra) {
self.ra = image.ra;
}
if (!self.dec) {
self.dec = image.dec;
}
if (!self.fov) {
self.fov = image.fov;
}
self.children.push(image)
self.children.push(image);
hduIdx += 1;
hduIdx += 1;
});
// Call the success callback on the first HDU image parsed
if (self.successCallback) {
self.successCallback(
self.children[0].ra,
self.children[0].dec,
self.children[0].fov,
self.children[0]
);
}
return self;
})
.catch((e) => {
// This error result from a promise
// If I throw it, it will not be catched because
// it is run async
self.view.removeImageLayer(layer);
return Promise.reject(e);
});
// Call the success callback on the first HDU image parsed
if (self.successCallback) {
self.successCallback(
self.children[0].ra,
self.children[0].dec,
self.children[0].fov,
self.children[0]
);
}
return self;
}).catch((e) => {
if (self.errorCallback) {
self.errorCallback()
}
// This error result from a promise
// If I throw it, it will not be catched because
// it is run async
self.view.removeImageLayer(layer)
return Promise.reject(e);
});
return promise;
};
@@ -234,9 +274,9 @@ export let ImageFITS = (function () {
};
// FITS images does not mean to be used for storing planetary data
ImageFITS.prototype.isPlanetaryBody = function() {
ImageFITS.prototype.isPlanetaryBody = function () {
return false;
}
};
// @api
ImageFITS.prototype.focusOn = function () {
@@ -280,4 +320,3 @@ export let ImageFITS = (function () {
return ImageFITS;
})();

861
src/js/ImageHiPS.js Normal file
View File

@@ -0,0 +1,861 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File ImageHiPS
*
* Authors: Thomas Boch & Matthieu Baumann [CDS]
*
*****************************************************************************/
import { Utils } from "./Utils";
import { ALEvent } from "./events/ALEvent.js";
import { ColorCfg } from "./ColorCfg.js";
import { HiPSProperties } from "./HiPSProperties.js";
import { HiPSCache } from "./DefaultHiPSCache.js";
let PropertyParser = {};
// Utilitary functions for parsing the properties and giving default values
/// Mandatory tileSize property
PropertyParser.tileSize = function (properties) {
let tileSize =
(properties &&
properties.hips_tile_width &&
+properties.hips_tile_width) ||
512;
// Check if the tile width size is a power of 2
if (tileSize & (tileSize - 1 !== 0)) {
tileSize = 512;
}
return tileSize;
};
/// Mandatory frame property
PropertyParser.cooFrame = function (properties) {
let cooFrame =
(properties && properties.hips_body && "ICRSd") ||
(properties && properties.hips_frame) ||
"ICRS";
return cooFrame;
};
/// Mandatory maxOrder property
PropertyParser.maxOrder = function (properties) {
let maxOrder =
properties && properties.hips_order && +properties.hips_order;
return maxOrder;
};
/// Mandatory minOrder property
PropertyParser.minOrder = function (properties) {
const minOrder =
(properties &&
properties.hips_order_min &&
+properties.hips_order_min) ||
0;
return minOrder;
};
PropertyParser.formats = function (properties) {
let formats = (properties && properties.hips_tile_format) || "jpeg";
formats = formats.split(" ").map((fmt) => fmt.toLowerCase());
return formats;
};
PropertyParser.initialFov = function (properties) {
let initialFov =
properties &&
properties.hips_initial_fov &&
+properties.hips_initial_fov;
if (initialFov && initialFov < 0.00002777777) {
initialFov = 360;
}
return initialFov;
};
PropertyParser.skyFraction = function (properties) {
const skyFraction =
(properties &&
properties.moc_sky_fraction &&
+properties.moc_sky_fraction) ||
0.0;
return skyFraction;
};
PropertyParser.cutouts = function (properties) {
let cuts =
properties &&
properties.hips_pixel_cut &&
properties.hips_pixel_cut.split(" ");
const minCutout = cuts && parseFloat(cuts[0]);
const maxCutout = cuts && parseFloat(cuts[1]);
return [minCutout, maxCutout];
};
PropertyParser.bitpix = function (properties) {
const bitpix =
properties &&
properties.hips_pixel_bitpix &&
+properties.hips_pixel_bitpix;
return bitpix;
};
PropertyParser.isPlanetaryBody = function (properties) {
return properties && properties.hips_body !== undefined;
};
/**
* @typedef {Object} ImageHiPSOptions
*
* @property {string} [name] - The name of the survey to be displayed in the UI
* @property {Function} [successCallback] - A callback executed when the HiPS has been loaded
* @property {Function} [errorCallback] - A callback executed when the HiPS could not be loaded
* @property {string} [imgFormat] - Formats accepted 'webp', 'png', 'jpeg' or 'fits'. Will raise an error if the HiPS does not contain tiles in this format
* @property {CooFrame} [cooFrame="J2000"] - Coordinate frame of the survey tiles
* @property {number} [maxOrder] - The maximum HEALPix order of the HiPS, i.e the HEALPix order of the most refined tile images of the HiPS.
* @property {number} [numBitsPerPixel] - Useful if you want to display the FITS tiles of a HiPS. It specifies the number of bits per pixel. Possible values are:
* -64: double, -32: float, 8: unsigned byte, 16: short, 32: integer 32 bits, 64: integer 64 bits
* @property {number} [tileSize] - The width of the HEALPix tile images. Mostly 512 pixels but can be 256, 128, 64, 32
* @property {number} [minOrder] - If not given, retrieved from the properties of the survey.
* @property {boolean} [longitudeReversed=false] - Set it to True for planetary survey visualization
* @property {number} [opacity=1.0] - Opacity of the survey or image (value between 0 and 1).
* @property {string} [colormap="native"] - The colormap configuration for the survey or image.
* @property {string} [stretch="linear"] - The stretch configuration for the survey or image.
* @property {boolean} [reversed=false] - If true, the colormap is reversed; otherwise, it is not reversed.
* @property {number} [minCut] - The minimum cut value for the color configuration. If not given, 0.0 for JPEG/PNG surveys, the value of the property file for FITS surveys
* @property {number} [maxCut] - The maximum cut value for the color configuration. If not given, 1.0 for JPEG/PNG surveys, the value of the property file for FITS surveys
* @property {boolean} [additive=false] - If true, additive blending is applied; otherwise, it is not applied.
* @property {number} [gamma=1.0] - The gamma correction value for the color configuration.
* @property {number} [saturation=0.0] - The saturation value for the color configuration.
* @property {number} [brightness=0.0] - The brightness value for the color configuration.
* @property {number} [contrast=0.0] - The contrast value for the color configuration.
*/
export let ImageHiPS = (function () {
/**
* The object describing an image survey
*
* @class
* @constructs ImageHiPS
*
* @param {string} id - Mandatory unique identifier for the layer. Can be an arbitrary name
* @param {string} url - Can be an url to the survey or a "CDS" ID pointing towards a HiPS. One can found the list of IDs {@link https://aladin.cds.unistra.fr/hips/list| here}
* @param {ImageHiPSOptions} [options] - The option for the survey
*
* @description Giving a CDS ID will do a query to the MOCServer first to retrieve metadata. Then it will also check for the presence of faster HiPS nodes to choose a faster url to query to tiles from.
*/
function ImageHiPS(id, url, options) {
this.added = false;
// Unique identifier for a survey
this.id = id;
this.name = (options && options.name) || undefined;
this.url = url;
this.maxOrder = options.maxOrder;
this.minOrder = options.minOrder || 0;
this.cooFrame = options.cooFrame;
this.tileSize = options.tileSize;
this.skyFraction = options.skyFraction;
this.longitudeReversed =
options.longitudeReversed === undefined
? false
: options.longitudeReversed;
this.imgFormat = options.imgFormat;
this.numBitsPerPixel = options.numBitsPerPixel;
this.creatorDid = options.creatorDid;
this.errorCallback = options.errorCallback;
this.successCallback = options.successCallback;
this.colorCfg = new ColorCfg(options);
}
ImageHiPS.prototype.setView = function (view) {
let self = this;
// do not allow to call setView multiple times otherwise
// the querying to the properties and the search to the best
// HiPS node will be done again for the same imageHiPS
if (self.view) {
return;
}
self.view = view;
let isMOCServerToBeQueried = true;
if (this.imgFormat === "fits") {
// a fits is given
isMOCServerToBeQueried = !(
this.maxOrder &&
this.url &&
this.imgFormat &&
this.tileSize &&
this.cooFrame &&
this.numBitsPerPixel
);
} else {
isMOCServerToBeQueried = !(
this.maxOrder &&
this.url &&
this.imgFormat &&
this.tileSize &&
this.cooFrame
);
}
self.query = (async () => {
if (isMOCServerToBeQueried) {
let isCDSId = false;
let properties = await HiPSProperties.fetchFromUrl(self.url)
/*.catch((e) => {
// try with the proxy
url = Utils.handleCORSNotSameOrigin(url).href;
return HiPSProperties.fetchFromUrl(url);
})*/
.catch(async (e) => {
// url not valid so we try with the id
try {
isCDSId = true;
// the url stores a "CDS ID" we take it prioritaly
// if the url is null, take the id, this is for some tests
// to pass because some users might just give null as url param and a "CDS ID" as id param
let id = self.url || self.id;
return await HiPSProperties.fetchFromID(id);
} catch (e) {
throw e;
}
});
//obsTitle = properties.obs_title;
self.creatorDid = properties.creator_did || self.creatorDid;
// url
if (isCDSId) {
self.url = properties.hips_service_url;
if (!self.url) {
throw "no valid service URL for retrieving the tiles";
}
self.url = Utils.fixURLForHTTPS(self.url);
// Request all the properties to see which mirror is the fastest
HiPSProperties.getFasterMirrorUrl(properties, self.url)
.then((url) => {
if (self.url !== url) {
console.info(
"Change url of ",
self.id,
" from ",
self.url,
" to ",
url
);
self.url = url;
// save the new url to the cache
self._saveInCache();
// If added to the backend, then we need to tell it the url has changed
if (self.added) {
self.view.wasm.setHiPSUrl(
self.creatorDid,
url
);
}
}
})
.catch((e) => {
console.error(self);
console.error(e);
});
}
// Max order
self.maxOrder =
PropertyParser.maxOrder(properties) || self.maxOrder;
// Tile size
self.tileSize =
PropertyParser.tileSize(properties) || self.tileSize;
// Tile formats
self.formats =
PropertyParser.formats(properties) || self.formats;
// min order
self.minOrder =
PropertyParser.minOrder(properties) || self.minOrder;
// Frame
self.cooFrame =
PropertyParser.cooFrame(properties) || self.cooFrame;
// sky fraction
self.skyFraction = PropertyParser.skyFraction(properties);
// Initial fov/ra/dec
self.initialFov = PropertyParser.initialFov(properties);
self.initialRa =
properties &&
properties.hips_initial_ra &&
+properties.hips_initial_ra;
self.initialDec =
properties &&
properties.hips_initial_dec &&
+properties.hips_initial_dec;
// Cutouts
const cutoutFromProperties = PropertyParser.cutouts(properties);
self.minCut = cutoutFromProperties[0];
self.maxCut = cutoutFromProperties[1];
// Bitpix
self.numBitsPerPixel =
PropertyParser.bitpix(properties) || self.numBitsPerPixel;
// HiPS body
if (properties.hips_body) {
self.hipsBody = properties.hips_body;
// Use the property to define and check some user given infos
// Longitude reversed
self.longitudeReversed = true;
}
// Give a better name if we have the HiPS metadata
self.name = self.name || properties.obs_title;
}
self.name = self.name || self.id || self.url;
self.name = self.name.replace(/ +/g, ' ');
self.creatorDid = self.creatorDid || self.id || self.url;
// Image format
if (self.imgFormat) {
// transform to lower case
self.imgFormat = self.imgFormat.toLowerCase();
// convert JPG -> JPEG
if (self.imgFormat === "jpg") {
self.imgFormat = "jpeg";
}
// user wants a fits but the properties tells this format is not available
if (
self.imgFormat === "fits" &&
self.formats &&
self.formats.indexOf("fits") < 0
) {
throw self.name + " does not provide fits tiles";
}
if (
self.imgFormat === "webp" &&
self.formats &&
self.formats.indexOf("webp") < 0
) {
throw self.name + " does not provide webp tiles";
}
if (
self.imgFormat === "png" &&
self.formats &&
self.formats.indexOf("png") < 0
) {
throw self.name + " does not provide png tiles";
}
if (
self.imgFormat === "jpeg" &&
self.formats &&
self.formats.indexOf("jpeg") < 0
) {
throw self.name + " does not provide jpeg tiles";
}
} else {
// user wants nothing then we choose one from the properties
if (self.formats.indexOf("webp") >= 0) {
self.imgFormat = "webp";
} else if (self.formats.indexOf("png") >= 0) {
self.imgFormat = "png";
} else if (self.formats.indexOf("jpeg") >= 0) {
self.imgFormat = "jpeg";
} else if (self.formats.indexOf("fits") >= 0) {
self.imgFormat = "fits";
} else {
throw (
"Unsupported format(s) found in the properties: " +
self.formats
);
}
}
// Cutouts
let minCut, maxCut;
if (self.imgFormat === "fits") {
// Take into account the default cuts given by the property file (this is true especially for FITS HiPSes)
minCut = self.colorCfg.minCut || self.minCut || 0.0;
maxCut = self.colorCfg.maxCut || self.maxCut || 1.0;
} else {
minCut = self.colorCfg.minCut || 0.0;
maxCut = self.colorCfg.maxCut || 1.0;
}
self.colorCfg.setCuts(minCut, maxCut);
// Coo frame
if (
self.cooFrame == "ICRS" ||
self.cooFrame == "ICRSd" ||
self.cooFrame == "equatorial" ||
self.cooFrame == "j2000"
) {
self.cooFrame = "ICRS";
} else if (self.cooFrame == "galactic") {
self.cooFrame = "GAL";
} else {
self.cooFrame = "ICRS";
console.warn(
"Invalid cooframe given: " +
self.cooFrame +
'. Coordinate systems supported: "ICRS", "ICRSd", "j2000" or "galactic". ICRS is chosen by default'
);
}
self.formats = self.formats || [self.imgFormat];
self._saveInCache();
return self;
})()
};
ImageHiPS.prototype._saveInCache = function () {
let self = this;
let colorOpt = Object.fromEntries(Object.entries(this.colorCfg));
let surveyOpt = {
id: self.id,
creatorDid: self.creatorDid,
name: self.name,
url: self.url,
skyFraction: self.skyFraction,
cooFrame: self.cooFrame,
maxOrder: self.maxOrder,
tileSize: self.tileSize,
imgFormat: self.imgFormat,
successCallback: self.successCallback,
errorCallback: self.errorCallback,
...colorOpt,
};
if (self.numBitsPerPixel) {
surveyOpt.numBitsPerPixel = self.numBitsPerPixel;
}
HiPSCache.append(self.id, {
// Erase by the cache already put values which is considered
// as the ground truth
...HiPSCache.get[self.id],
// append new important infos from the properties queried
...surveyOpt,
});
};
/**
* Checks if the ImageHiPS represents a planetary body.
*
* This method returns a boolean indicating whether the ImageHiPS corresponds to a planetary body, e.g. the earth or a celestial body.
*
* @memberof ImageHiPS
*
* @returns {boolean} Returns true if the ImageHiPS represents a planetary body; otherwise, returns false.
*/
ImageHiPS.prototype.isPlanetaryBody = function () {
return this.hipsBody !== undefined;
};
/**
* Sets the image format for the ImageHiPS.
*
* This method updates the image format of the ImageHiPS, performs format validation, and triggers the update of metadata.
*
* @memberof ImageHiPS
*
* @param {string} format - The desired image format. Should be one of ["fits", "png", "jpg", "webp"].
*
* @throws {string} Throws an error if the provided format is not one of the supported formats or if the format is not available for the specific ImageHiPS.
*/
ImageHiPS.prototype.setImageFormat = function (format) {
let self = this;
self.query.then(() => {
self._updateMetadata(() => {
let imgFormat = format.toLowerCase();
if (
imgFormat !== "fits" &&
imgFormat !== "png" &&
imgFormat !== "jpg" &&
imgFormat !== "jpeg" &&
imgFormat !== "webp"
) {
throw 'Formats must lie in ["fits", "png", "jpg", "webp"]';
}
if (imgFormat === "jpg") {
imgFormat = "jpeg";
}
// Passed the check, we erase the image format with the new one
// We do nothing if the imgFormat is the same
if (self.imgFormat === imgFormat) {
return;
}
// Check the properties to see if the given format is available among the list
// If the properties have not been retrieved yet, it will be tested afterwards
const availableFormats = self.formats;
// user wants a fits but the metadata tells this format is not available
if (
imgFormat === "fits" &&
availableFormats.indexOf("fits") < 0
) {
throw self.id + " does not provide fits tiles";
}
if (
imgFormat === "webp" &&
availableFormats.indexOf("webp") < 0
) {
throw self.id + " does not provide webp tiles";
}
if (
imgFormat === "png" &&
availableFormats.indexOf("png") < 0
) {
throw self.id + " does not provide png tiles";
}
if (
imgFormat === "jpeg" &&
availableFormats.indexOf("jpeg") < 0
) {
throw self.id + " does not provide jpeg tiles";
}
// Switch from png/webp/jpeg to fits
if (
(self.imgFormat === "png" ||
self.imgFormat === "webp" ||
self.imgFormat === "jpeg") &&
imgFormat === "fits"
) {
if (self.minCut && self.maxCut) {
// reset cuts to those given from the properties
self.setCuts(self.minCut, self.maxCut);
}
// Switch from fits to png/webp/jpeg
} else if (self.imgFormat === "fits") {
self.setCuts(0.0, 1.0);
}
// Check if it is a fits
self.imgFormat = imgFormat;
});
});
};
/**
* Sets the opacity factor when rendering the ImageHiPS
*
* @memberof ImageHiPS
*
* @returns {string[]} Returns the formats accepted for the survey, i.e. the formats of tiles that are availables. Could be PNG, WEBP, JPG and FITS.
*/
ImageHiPS.prototype.getAvailableFormats = function () {
return this.formats;
};
/**
* Sets the opacity factor when rendering the ImageHiPS
*
* @memberof ImageHiPS
*
* @param {number} opacity - Opacity of the survey to set. Between 0 and 1
*/
ImageHiPS.prototype.setOpacity = function (opacity) {
this._updateMetadata(() => {
this.colorCfg.setOpacity(opacity);
});
};
/**
* Sets the blending mode when rendering the ImageHiPS
*
* @memberof ImageHiPS
*
* @param {boolean} [additive=false] -
*
* @description Two rendering modes are availables i.e. the default one and the additive one.
* When rendering this survey on top of the already rendered ones, the final color of the screen is computed like:
* <br>
* <br>opacity * this_survey_color + (1 - opacity) * already_rendered_color for the default mode
* <br>opacity * this_survey_color + already_rendered_color for the additive mode
* <br>
* <br>
* Additive mode allows you to do linear survey color combination i.e. let's define 3 surveys named s1, s2, s3. Each could be associated to one color channel, i.e. s1 with red, s2 with green and s3 with the blue color channel.
* If the additive blending mode is enabled, then the final pixel color of your screen will be: rgb = [s1_opacity * s1_color; s2_opacity * s2_color; s3_opacity * s3_color]
*/
ImageHiPS.prototype.setBlendingConfig = function (additive = false) {
this._updateMetadata(() => {
this.colorCfg.setBlendingConfig(additive);
});
};
/**
* Sets the colormap when rendering the ImageHiPS.
*
* @memberof ImageHiPS
*
* @param {string} [colormap="grayscale"] - The colormap label to use. See {@link https://matplotlib.org/stable/users/explain/colors/colormaps.html|here} for more info about colormaps.
* Possible values are:
* <br>"blues"
* <br>"cividis"
* <br>"cubehelix"
* <br>"eosb"
* <br>"grayscale"
* <br>"inferno"
* <br>"magma"
* <br>"native"
* <br>"parula"
* <br>"plasma"
* <br>"rainbow"
* <br>"rdbu"
* <br>"rdylbu"
* <br>"redtemperature"
* <br>"sinebow"
* <br>"spectral"
* <br>"summer"
* <br>"viridis"
* <br>"ylgnbu"
* <br>"ylorbr"
* <br>"red"
* <br>"green"
* <br>"blue"
* @param {Object} [options] - Options for the colormap
* @param {string} [options.stretch] - Stretching function of the colormap. Possible values are 'linear', 'asinh', 'log', 'sqrt', 'pow'. If no given, will not change it.
* @param {boolean} [options.reversed=false] - Reverse the colormap axis.
*/
ImageHiPS.prototype.setColormap = function (colormap, options) {
this._updateMetadata(() => {
this.colorCfg.setColormap(colormap, options);
});
};
/**
* Sets the gamma correction factor for the ImageHiPS.
*
* This method updates the gamma of the ImageHiPS.
*
* @memberof ImageHiPS
*
* @param {number} lowCut - The low cut value to set for the ImageHiPS.
* @param {number} highCut - The high cut value to set for the ImageHiPS.
*/
ImageHiPS.prototype.setCuts = function (lowCut, highCut) {
this._updateMetadata(() => {
this.colorCfg.setCuts(lowCut, highCut);
});
};
/**
* Sets the gamma correction factor for the ImageHiPS.
*
* This method updates the gamma of the ImageHiPS.
*
* @memberof ImageHiPS
*
* @param {number} gamma - The saturation value to set for the ImageHiPS. Between 0.1 and 10
*/
ImageHiPS.prototype.setGamma = function (gamma) {
this._updateMetadata(() => {
this.colorCfg.setGamma(gamma);
});
};
/**
* Sets the saturation for the ImageHiPS.
*
* This method updates the saturation of the ImageHiPS.
*
* @memberof ImageHiPS
*
* @param {number} saturation - The saturation value to set for the ImageHiPS. Between 0 and 1
*/
ImageHiPS.prototype.setSaturation = function (saturation) {
this._updateMetadata(() => {
this.colorCfg.setSaturation(saturation);
});
};
/**
* Sets the brightness for the ImageHiPS.
*
* This method updates the brightness of the ImageHiPS.
*
* @memberof ImageHiPS
*
* @param {number} brightness - The brightness value to set for the ImageHiPS. Between 0 and 1
*/
ImageHiPS.prototype.setBrightness = function (brightness) {
this._updateMetadata(() => {
this.colorCfg.setBrightness(brightness);
});
};
/**
* Sets the contrast for the ImageHiPS.
*
* This method updates the contrast of the ImageHiPS and triggers the update of metadata.
*
* @memberof ImageHiPS
*
* @param {number} contrast - The contrast value to set for the ImageHiPS. Between 0 and 1
*/
ImageHiPS.prototype.setContrast = function (contrast) {
this._updateMetadata(() => {
this.colorCfg.setContrast(contrast);
});
};
// Private method for updating the backend with the new meta
ImageHiPS.prototype._updateMetadata = function (callback) {
if (callback) {
callback();
}
// Tell the view its meta have changed
try {
if (this.added) {
this.view.wasm.setImageMetadata(this.layer, {
...this.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: this.imgFormat,
});
// once the meta have been well parsed, we can set the meta
ALEvent.HIPS_LAYER_CHANGED.dispatchedTo(this.view.aladinDiv, {
layer: this,
});
}
// save it in the JS HiPS cache
this._saveInCache();
} catch (e) {
// Display the error message
console.error(e);
}
};
ImageHiPS.prototype.add = function (layer) {
this.layer = layer;
let self = this;
this.view.wasm.addImageHiPS({
layer,
properties: {
creatorDid: self.creatorDid,
url: self.url,
maxOrder: self.maxOrder,
cooFrame: self.cooFrame,
tileSize: self.tileSize,
formats: self.formats,
bitpix: self.numBitsPerPixel,
skyFraction: self.skyFraction,
minOrder: self.minOrder,
hipsInitialFov: self.initialFov,
hipsInitialRa: self.initialRa,
hipsInitialDec: self.initialDec,
isPlanetaryBody: self.isPlanetaryBody(),
hipsBody: self.hipsBody,
},
meta: {
...this.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: this.imgFormat,
},
});
return Promise.resolve(this)
.then((hips) => {
if (hips.successCallback) {
hips.successCallback(hips)
}
return hips
});
};
// @api
ImageHiPS.prototype.toggle = function () {
if (this.colorCfg.getOpacity() != 0.0) {
this.colorCfg.setOpacity(0.0);
} else {
this.colorCfg.setOpacity(this.prevOpacity);
}
};
// @oldapi
ImageHiPS.prototype.setAlpha = ImageHiPS.prototype.setOpacity;
ImageHiPS.prototype.setColorCfg = function (colorCfg) {
this._updateMetadata(() => {
this.colorCfg = colorCfg;
});
};
// @api
ImageHiPS.prototype.getColorCfg = function () {
return this.colorCfg;
};
// @api
ImageHiPS.prototype.getOpacity = function () {
return this.colorCfg.getOpacity();
};
ImageHiPS.prototype.getAlpha = ImageHiPS.prototype.getOpacity;
// @api
ImageHiPS.prototype.readPixel = function (x, y) {
return this.view.wasm.readPixel(x, y, this.layer);
};
ImageHiPS.DEFAULT_SURVEY_ID = "CDS/P/DSS2/color";
return ImageHiPS;
})();

File diff suppressed because it is too large Load Diff

View File

@@ -51,19 +51,62 @@ export class MocServer {
expr: "dataproduct_type=image",
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
};
this._allHiPSes = Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
data: params,
dataType: 'json'
dataType: 'json',
desc: 'MOCServer query to get all the HiPS metadata'
})
}
return this._allHiPSes;
}
static getAllHiPSesInsideView(aladin) {
let params = {
//expr: "dataproduct_type=image||dataproduct_type=cube",
expr: "dataproduct_type=image",
get: "record",
fmt: "json",
fields: "ID",
};
try {
const corners = aladin.getFoVCorners(1, 'icrs');
let stc = 'Polygon '
for (var radec of corners) {
stc += radec[0] + ' ' + radec[1] + ' ';
}
params['stc'] = stc;
} catch (e) {}
return Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
data: params,
dataType: 'json',
desc: 'MOCServer: Retrieve HiPS inside FoV'
})
}
static getHiPSesFromIDs(ids) {
const params = {
//expr: "dataproduct_type=image||dataproduct_type=cube",
expr: "dataproduct_type=image&&ID=" + ids.join(','),
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
};
return Utils.loadFromUrls(MocServer.MIRRORS_HTTPS, {
data: params,
dataType: 'json'
})
}
static getAllCatalogHiPSes() {
if (!this._allCatalogHiPSes) {
const params = {

View File

@@ -117,7 +117,12 @@ export let Source = (function() {
}
}
// function called when a source is clicked. Called by the View object
/**
* Simulates a click on the source
*
* @memberof Source
* @param {Footprint|Source} [obj] - If not given, the source is taken as the object to be selected
*/
Source.prototype.actionClicked = function(obj) {
if (this.catalog && this.catalog.onClick) {
var view = this.catalog.view;

View File

@@ -265,7 +265,7 @@ Utils.loadFromUrls = function (urls, options) {
const mode = options && options.mode || 'cors';
const contentType = options && options.contentType || undefined;
const dataType = (options && options.dataType) || 'text';
const desc = options && options.desc;
// A controller that can abort the query when a timeout is reached
const controller = new AbortController()
@@ -289,7 +289,9 @@ Utils.loadFromUrls = function (urls, options) {
cache: 'default',
// Abort the request when a timeout exceeded
signal: controller.signal,
dataType
dataType,
// message description
desc
}
if (contentType) {
@@ -387,13 +389,15 @@ Utils.fetch = function(params) {
if (params.success) {
return params.success(data)
}
return Promise.resolve(data);
})
.catch(e => {
if (params.error) {
params.error(e)
} else {
alert(e)
return params.error(e)
}
return Promise.reject(e);
})
.finally(() => {
ALEvent.RESOURCE_FETCHED.dispatchedTo(document, {task});

View File

@@ -50,8 +50,7 @@ import { ObsCore } from "./vo/ObsCore.js";
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
import { Layout } from "./gui/Layout.js";
import { SAMPActionButton } from "./gui/Button/SAMP.js";
import { ImageSurvey } from "./ImageSurvey.js";
import { ImageFITS } from "./ImageFITS.js";
import { HiPSCache } from "./DefaultHiPSCache.js";
export let View = (function () {
@@ -121,6 +120,42 @@ export let View = (function () {
this.aladinDiv.ondragover = Utils.dragOverHandler;
this.throttledPositionChanged = Utils.throttle(
() => {
var posChangedFn = this.aladin.callbacksByEventName && this.aladin.callbacksByEventName['positionChanged'];
if (typeof posChangedFn === 'function') {
var pos = this.aladin.pix2world(this.width / 2, this.height / 2, 'icrs');
if (pos !== undefined) {
try {
posChangedFn({
ra: pos[0],
dec: pos[1],
dragging: true
});
} catch(e) {
console.error(e)
}
}
}
},
View.CALLBACKS_THROTTLE_TIME_MS,
);
this.throttledZoomChanged = Utils.throttle(
() => {
const fov = this.fov;
// trigger callback only if FoV (zoom) has changed !
if (fov !== this.oldFov) {
const fovChangedFn = this.aladin.callbacksByEventName['zoomChanged'];
(typeof fovChangedFn === 'function') && fovChangedFn(fov);
// finally, save fov value
this.oldFov = fov;
}
},
View.CALLBACKS_THROTTLE_TIME_MS,
);
this.mustClearCatalog = true;
this.mode = View.PAN;
@@ -227,51 +262,13 @@ export let View = (function () {
self.fixLayoutDimensions();
})
} else {*/
let resizeLayout = () => {
self.fixLayoutDimensions();
}
let doit;
this.resizeObserver = new ResizeObserver(() => {
//clearTimeout(doit);
//doit = setTimeout(resizeLayout, 100);
resizeLayout();
self.fixLayoutDimensions();
});
self.resizeObserver.observe(this.aladinDiv)
this.throttledPositionChanged = Utils.throttle(
() => {
var posChangedFn = this.aladin.callbacksByEventName && this.aladin.callbacksByEventName['positionChanged'];
if (typeof posChangedFn === 'function') {
var pos = this.aladin.pix2world(this.width / 2, this.height / 2);
if (pos !== undefined) {
posChangedFn({
ra: pos[0],
dec: pos[1],
dragging: true
});
}
}
},
View.CALLBACKS_THROTTLE_TIME_MS,
);
this.throttledZoomChanged = Utils.throttle(
() => {
const fov = this.fov;
// trigger callback only if FoV (zoom) has changed !
if (fov !== this.oldFov) {
const fovChangedFn = this.aladin.callbacksByEventName['zoomChanged'];
(typeof fovChangedFn === 'function') && fovChangedFn(fov);
// finally, save fov value
this.oldFov = fov;
}
},
View.CALLBACKS_THROTTLE_TIME_MS,
);
self.fixLayoutDimensions();
self.redraw()
@@ -1099,7 +1096,7 @@ export let View = (function () {
}
view.debounceProgCatOnZoom();
view.throttledZoomChanged();
//view.throttledZoomChanged();
// Zoom heuristic
// First detect the device
@@ -1248,9 +1245,12 @@ export let View = (function () {
// Drawing code
//try {
this.moving = this.wasm.update(elapsedTime);
//} catch (e) {
// console.error(e)
//}
// inertia run throttled position
if (this.moving && this.aladin.callbacksByEventName && this.aladin.callbacksByEventName['positionChanged'] && this.wasm.isInerting()) {
// run the trottled position
this.throttledPositionChanged();
}
////// 2. Draw catalogues////////
const isViewRendering = this.wasm.isRendering();
@@ -1537,6 +1537,8 @@ export let View = (function () {
fovY = Math.min(fovY, 180);
ALEvent.ZOOM_CHANGED.dispatchedTo(this.aladinDiv, { fovX: fovX, fovY: fovY });
this.throttledZoomChanged();
};
/**
@@ -1612,7 +1614,7 @@ export let View = (function () {
Promise.allSettled(this.promises)
.then(() => imageLayerPromise)
// The promise is resolved and we now have access
// to the image layer objet (whether it is an ImageSurvey or an ImageFITS)
// to the image layer objet (whether it is an ImageHiPS or an ImageFITS)
.then((imageLayer) => {
// Add to the backend
const promise = imageLayer.add(layer);
@@ -1641,8 +1643,11 @@ export let View = (function () {
})
.catch((e) => {
// remove it from the cache
delete ImageSurvey.cache[imageLayer.id]
delete ImageFITS.cache[imageLayer.id]
HiPSCache.delete(imageLayer.id)
if (imageLayer.errorCallback) {
imageLayer.errorCallback(e);
}
throw e;
})
@@ -1661,7 +1666,7 @@ export let View = (function () {
if (noMoreLayersToWaitFor) {
if (self.empty) {
// no promises to launch!
//self.aladin.setBaseImageLayer(self.aladin.createImageSurvey(ImageSurvey.DEFAULT_SURVEY_ID));
//self.aladin.setBaseImageLayer(self.aladin.createImageSurvey(ImageHiPS.DEFAULT_SURVEY_ID));
} else {
// there is surveys that have been queried
// rename the first overlay layer to "base"
@@ -1740,7 +1745,7 @@ export let View = (function () {
if (this.overlayLayers.length === 0) {
this.empty = true;
} else if (this.selectedLayer === layer) {
// If the layer removed was selected then we select the base layer
// If the layer removed was selected then we select the last layer
this.selectLayer(this.overlayLayers[this.overlayLayers.length - 1]);
}
@@ -1864,9 +1869,10 @@ export let View = (function () {
ra = parseFloat(ra);
dec = parseFloat(dec);
if (!ra || !dec) {
if (!Number.isFinite(ra) || !Number.isFinite(dec)) {
return;
}
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
//this.updateLocation({lon: this.viewCenter.lon, lat: this.viewCenter.lat});
@@ -1928,7 +1934,10 @@ export let View = (function () {
if (layer.type == 'catalog' || layer.type == 'progressivecat') {
indexToDelete = this.catalogs.indexOf(layer);
this.catalogs.splice(indexToDelete, 1);
this.unselectObjects();
}
else if (layer.type == 'moc') {
indexToDelete = this.mocs.indexOf(layer);

View File

@@ -48,25 +48,34 @@ import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
9/3600, 8/3600, 7/3600, 6/3600, 5/3600, 4/3600, 3/3600, 2/3600, 1/3600,
9/36000, 8/36000, 7/36000, 6/36000, 5/36000, 4/36000, 3/36000, 2/36000, 1/36000
];
Zoom.MAX_IDX_DELTA_PER_TROTTLE = 2;
Zoom.MAX_IDX_DELTA_PER_TROTTLE = 6;
Zoom.determineNextFov = function(view, amount) {
if (!view.idx)
view.idx = Utils.binarySearch(Zoom.LEVELS, view.fov);
const currIdx = Utils.binarySearch(Zoom.LEVELS, view.fov);
let deltaIdx = amount;
view.idx += deltaIdx;
let nextIdx = currIdx + deltaIdx;
// clamp to the array indices
if (view.idx < 0) {
view.idx = 0
if (nextIdx < 0) {
nextIdx = 0
}
if (view.idx >= Zoom.LEVELS.length) {
view.idx = Zoom.LEVELS.length - 1
if (nextIdx >= Zoom.LEVELS.length) {
nextIdx = Zoom.LEVELS.length - 1
}
return Zoom.LEVELS[view.idx];
let nextFov = Zoom.LEVELS[nextIdx];
if (view.minFoV) {
nextFov = Math.max(nextFov, view.minFoV)
}
if (view.maxFoV) {
nextFov = Math.min(nextFov, view.maxFoV)
}
return nextFov;
}
Zoom.prototype.apply = function(options) {

View File

@@ -53,7 +53,7 @@ export class ALEvent {
static HIPS_LAYER_RENAMED = new ALEvent("AL:HiPSLayer.renamed");
static HIPS_LAYER_SWAP = new ALEvent("AL:HiPSLayer.swap");
static HIPS_LIST_UPDATED = new ALEvent("AL:HiPSList.updated");
static HIPS_CACHE_UPDATED = new ALEvent("AL:HiPSCache.updated");
static HIPS_LAYER_CHANGED = new ALEvent("AL:HiPSLayer.changed");

View File

@@ -0,0 +1,327 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { MocServer } from "../../MocServer.js";
import { Box } from "../Widgets/Box.js";
import { Dropdown } from "../Input/Dropdown.js";
import filterOnUrl from "../../../../assets/icons/filter-on.svg";
import filterOffUrl from "../../../../assets/icons/filter-off.svg";
import { Input } from "../Widgets/Input.js";
import { TogglerActionButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
import { HiPSFilterBox } from "./HiPSFilterBox.js";
import A from "../../A.js";
import { Utils } from "../../Utils.ts";
import { ActionButton } from "../Widgets/ActionButton.js";
import infoIconUrl from "../../../../assets/icons/info.svg"
/******************************************************************************
* Aladin Lite project
*
* File gui/HiPSBrowserBox.js
*
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
export class HiPSBrowserBox extends Box {
static HiPSList = {};
constructor(aladin, options) {
let self;
MocServer.getAllHiPSes().then((HiPSes) => {
// Fill the HiPSList from the MOCServer
HiPSes.forEach((h) => {
HiPSBrowserBox.HiPSList[h.obs_title] = h;
});
// Initialize the autocompletion without any filtering
self._filterHiPSList({})
});
const _parseHiPS = (e) => {
const value = e.target.value;
let image;
// A user can put an url
try {
image = new URL(value).href;
} catch (e) {
// Or he can select a HiPS from the list given
const hips = HiPSBrowserBox.HiPSList[value];
if (hips) {
image = hips.ID || hips.hips_service_url;
} else {
// Finally if not found, interpret the input text value as the HiPS (e.g. ID)
image = value;
}
}
if (image) {
self._addHiPS(image)
self.searchDropdown.update({title: value});
}
};
let searchDropdown = new Dropdown(aladin, {
name: "HiPS browser",
placeholder: "Browse a HiPS by an URL, ID or keywords",
tooltip: {
global: true,
aladin,
content: 'HiPS url, ID or keyword accepted',
},
actions: {
focus(e) {
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
keydown(e) {
e.stopPropagation();
if (e.key === 'Enter') {
e.preventDefault()
_parseHiPS(e)
}
},
input(e) {
self.infoCurrentHiPSBtn.update({
disable: true,
})
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
change(e) {
_parseHiPS(e)
},
},
});
let filterEnabler = Input.checkbox({
name: "filter-enabler",
checked: false,
tooltip: {
content: "Filter off",
position: {direction: 'left'},
},
click(e) {
let on = e.target.checked;
self.filterBox.enable(on);
if (!on) {
// if the filter has been disabled we also need to update
// the autocompletion list of the search dropdown
// We give no filter params
self._filterHiPSList({});
}
filterBtn.update({
icon: {
url: on ? filterOnUrl : filterOffUrl,
monochrome: true,
},
});
filterEnabler.update({
tooltip: {
content: on
? "Filter on"
: "Filter off",
position: {direction: 'left'},
},
checked: on,
});
},
});
let infoCurrentHiPSBtn = new ActionButton({
disable: true,
icon: {
size: 'medium',
monochrome: true,
url: infoIconUrl,
},
tooltip: {
global: true,
aladin,
content: "More about that survey?"
}
});
let filterBtn = new TogglerActionButton({
icon: {
url: filterOffUrl,
monochrome: true,
},
size: "small",
tooltip: {
content: "Want to filter HiPS surveys by criteria ?",
position: { direction: "top" },
},
toggled: false,
actionOn: (e) => {
self.filterBox._show({
position: {
nextTo: filterBtn,
direction: "right",
aladin,
},
});
},
actionOff: (e) => {
self.filterBox._hide();
},
});
super(
{
close: true,
header: {
title: "HiPS browser",
},
onDragged: () => {
if (self.filterBtn.toggled) {
self.filterBtn.toggle();
}
},
classList: ['aladin-HiPS-browser-box'],
content: Layout.vertical([
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn])]),
]),
...options,
},
aladin.aladinDiv
);
this.filterBox = new HiPSFilterBox(aladin, {
callback: (params) => {
self._filterHiPSList(params);
},
})
this.filterBox._hide();
this.searchDropdown = searchDropdown;
this.filterBtn = filterBtn;
this.aladin = aladin;
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
self = this;
this.filterCallback = (HiPS, params) => {
if (!HiPS.obs_regime || (
params.regime &&
HiPS.obs_regime &&
params.regime.toLowerCase() !==
HiPS.obs_regime.toLowerCase()
)) {
return false;
}
if (Array.isArray(params.spatial) && HiPS.ID && !(params.spatial.includes(HiPS.ID))) {
return false;
}
if (!HiPS.hips_tile_width || !HiPS.hips_order)
return false;
if (params.resolution) {
let pixelHEALPixOrder = Math.log2(HiPS.hips_tile_width) + HiPS.hips_order;
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
if (resPixel > params.resolution)
return false;
}
return true;
};
}
_addHiPS(id) {
let self = this;
let hips = A.imageHiPS(id, {
successCallback: (hips) => {
self.searchDropdown.removeClass('aladin-not-valid');
self.searchDropdown.addClass('aladin-valid');
self.infoCurrentHiPSBtn.update({
disable: false,
action(e) {
window.open(hips.url);
}
})
},
errorCallback: (e) => {
self.searchDropdown.removeClass('aladin-valid');
self.searchDropdown.addClass('aladin-not-valid');
}
});
this.aladin.setOverlayImageLayer(hips, self.layer);
}
// This method is executed only if the filter is enabled
_filterHiPSList(params) {
console.log("update dropdown")
let self = this;
let HiPSIDs = [];
for (var key in HiPSBrowserBox.HiPSList) {
let HiPS = HiPSBrowserBox.HiPSList[key];
// apply filtering
if (
self.filterCallback &&
self.filterCallback(HiPS, params)
) {
// search with the name or id
HiPSIDs.push(HiPS.obs_title);
}
}
self.searchDropdown.update({ options: HiPSIDs });
}
_hide() {
if (this.filterBox)
this.filterBox.signalBrowserStatus(true)
if (this.filterBtn && this.filterBtn.toggled) {
this.filterBtn.toggle();
}
super._hide()
}
_show(options) {
// Regenerate a new layer name
this.layer = Utils.uuidv4()
if (this.filterBox)
this.filterBox.signalBrowserStatus(false)
super._show(options)
}
}

View File

@@ -0,0 +1,230 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { Box } from "../Widgets/Box.js";
import { 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";
/******************************************************************************
* Aladin Lite project
*
* File gui/HiPSBrowserBox.js
*
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
export class HiPSFilterBox extends Box {
constructor(aladin, options) {
let self;
let regimeBtn = new TogglerActionButton({
content: 'Regime',
tooltip: {content: 'Observation regime', position: {direction: 'bottom'}},
toggled: true,
actionOn: () => {
self._triggerFilteringCallback();
},
actionOff: () => {
self._triggerFilteringCallback();
}
});
let spatialBtn = new TogglerActionButton({
content: 'Inside view',
tooltip: {content: 'Check for HiPS having observation in the view!', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._requestMOCServer();
},
actionOff: () => {
self._triggerFilteringCallback();
}
});
let resolutionBtn = new TogglerActionButton({
content: 'Pixel res',
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
},
actionOff: () => {
self._triggerFilteringCallback();
}
});
let logSlider = new Input({
label: "Max res [°/px]:",
name: "res",
value: 0.1,
type: 'range',
cssStyle: {
width: '100%'
},
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
stretch: "log",
min: 0.1 / 3600,
max: 0.1,
reversed: true,
change: (e, slider, deg) => {
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
let resolution = new Angle(deg);
self.params["resolution"] = resolution.degrees();
self._triggerFilteringCallback();
},
});
super(
{
classList: ['aladin-HiPS-filter-box'],
close: false,
content: Layout.vertical([
'<b>Filter by:</b>',
Layout.horizontal([regimeBtn, spatialBtn, resolutionBtn]),
'<b>Details:</b>',
new Form({
subInputs: [
{
type: "group",
subInputs: [
{
label: "Regime:",
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
],
},
],
}),
])
},
aladin.aladinDiv
);
self = this;
this.browserClosed = false;
this.callback = options.callback;
this.regimeBtn = regimeBtn;
this.spatialBtn = spatialBtn;
this.resolutionBtn = resolutionBtn;
this.params = {
regime: "Optical",
spatial: true,
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) {
filterParams['regime'] = this.params['regime']
}
if (this.spatialBtn.toggled) {
filterParams['spatial'] = this.params['spatial']
}
if (this.resolutionBtn.toggled) {
filterParams['resolution'] = this.params['resolution']
}
if (this.on && this.callback) {
this.callback(filterParams);
}
}
signalBrowserStatus(closed) {
this.browserClosed = closed;
// open
if (!closed) {
this._requestMOCServer()
}
}
enable(enable) {
this.on = enable;
this._triggerFilteringCallback();
}
}

View File

@@ -45,8 +45,7 @@ import { ColorCfg } from "../../ColorCfg.js";
constructor(aladin, options) {
super({
cssStyle: {
padding: '4px',
backgroundColor: 'black',
backgroundColor: '#333333'
},
close: false,
...options

View File

@@ -75,6 +75,7 @@ export class ServiceQueryBox extends Box {
Utils.loadFromUrls([url, Utils.handleCORSNotSameOrigin(url)], {timeout: 30000, dataType: 'blob'})
.then((blob) => {
const url = URL.createObjectURL(blob);
try {
let image = self.aladin.createImageFITS(url, name);
self.aladin.setOverlayImageLayer(image, Utils.uuidv4())
@@ -149,8 +150,8 @@ export class ServiceQueryBox extends Box {
let fov = new Angle(radius, 1).degrees();
//selectorBtn.update({tooltip: {content: 'center: ' + ra.toFixed(2) + ', ' + dec.toFixed(2) + '<br\>radius: ' + radius.toFixed(2), position: {direction: 'left'}}})
self.form.set('ra', +lon)
self.form.set('dec', +lat)
self.form.set('ra', lon)
self.form.set('dec', lat)
self.form.set('rad', fov)
} catch (e) {
alert(e, 'Cone search out of projection')

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@
import { CtxMenuActionButtonOpener } from "./CtxMenuOpener";
import stackOverlayIconUrl from './../../../../assets/icons/stack.svg';
import { OverlayStack } from "../CtxMenu/OverlayStack";
import { OverlayStackBox } from "../Box/StackBox";
import { TogglerActionButton } from "./Toggler";
/******************************************************************************

View File

@@ -1,817 +0,0 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File gui/Stack/Menu.js
*
*
* Author: Matthieu Baumann [CDS, matthieu.baumann@astro.unistra.fr]
*
*****************************************************************************/
import { CatalogQueryBox } from "../Box/CatalogQueryBox.js";
import { ALEvent } from "../../events/ALEvent.js";
import { Layout } from "../Layout.js";
import { ContextMenu } from "../Widgets/ContextMenu.js";
import { ActionButton } from "../Widgets/ActionButton.js";
import A from "../../A.js";
import { Utils } from "../../../js/Utils";
import { View } from "../../View.js";
import { HiPSSettingsBox } from "../Box/HiPSSettingsBox.js";
import { HiPSSelectorBox } from "../Box/HiPSSelectorBox.js";
import searchIconUrl from '../../../../assets/icons/search.svg';
import showIconUrl from '../../../../assets/icons/show.svg';
import hideIconUrl from '../../../../assets/icons/hide.svg';
import removeIconUrl from '../../../../assets/icons/remove.svg';
import settingsIconUrl from '../../../../assets/icons/settings.svg';
import { ImageFITS } from "../../ImageFITS.js";
import searchIconImg from '../../../../assets/icons/search.svg';
import { TogglerActionButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { ImageSurvey } from "../../ImageSurvey.js";
export class OverlayStack extends ContextMenu {
static previewImagesUrl = {
'AllWISE color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_allWISE_color.jpg',
'DSS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DSS2_color.jpg',
'DSS2 Red (F+R)': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DSS2_red.jpg',
'Fermi color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Fermi_color.jpg',
'GALEXGR6_7 NUV': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_GALEXGR6_7_color.jpg',
'GLIMPSE360': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_GLIMPSE360.jpg',
'Halpha': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_VTSS_Ha.jpg',
'IRAC color I1,I2,I4 - (GLIMPSE, SAGE, SAGE-SMC, SINGS)': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SPITZER_color.jpg',
'IRIS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_IRIS_color.jpg',
'Mellinger colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Mellinger_color.jpg',
'PanSTARRS DR1 color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_PanSTARRS_DR1_color-z-zg-g.jpg',
'2MASS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_2MASS_color.jpg',
'AKARI colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_AKARI_FIS_Color.jpg',
'SWIFT': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SWIFT_BAT_FLUX.jpg',
'VTSS-Ha': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Finkbeiner.jpg',
'XMM PN colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_XMM_PN_color.jpg',
'SDSS9 colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SDSS9_color.jpg',
};
static predefinedCats = {
simbad: {url: 'https://axel.u-strasbg.fr/HiPSCatService/SIMBAD', options: {id: 'simbad', name: 'SIMBAD', shape: 'circle', sourceSize: 8, color: '#318d80', onClick: 'showTable'}},
gaia: {url: 'https://axel.u-strasbg.fr/HiPSCatService/I/355/gaiadr3', options: {id: 'gaia-dr3', name: 'Gaia DR3', shape: 'square', sourceSize: 8, color: '#6baed6', onClick: 'showTable'}},
twomass: {url: 'https://axel.u-strasbg.fr/HiPSCatService/II/246/out', options: {id: '2mass', name: '2MASS', shape: 'plus', sourceSize: 8, color: '#dd2233', onClick: 'showTable'}}
};
// Constructor
constructor(aladin) {
let self;
super(aladin, {hideOnClick: (e) => {
// only hide the stack ctx menu but not the windows
super._hide();
}});
self = this;
this.aladin = aladin;
this.mode = 'stack';
this._addListeners();
this.mocHiPSUrls = {}
}
_addListeners() {
let self = this;
let updateOverlayList = () => {
// If it is shown, update it
if (!self.isHidden) {
// show will update the content of the stack
self.attach();
self.show();
}
};
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.GRAPHIC_OVERLAY_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_ADDED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_RENAMED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_SWAP.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_REMOVED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
updateOverlayList();
}
attach() {
let self = this;
const overlays = Array.from(this.aladin.getOverlays()).reverse().map((overlay) => {
return overlay;
});
const layers = Array.from(self.aladin.getImageOverlays()).reverse().map((name) => {
let overlay = self.aladin.getOverlayImageLayer(name);
return overlay;
});
let layout = [{
label: 'Add overlay',
subMenu: [
{
label: 'Catalogue',
subMenu: [
{
label: {
icon: {
url: 'https://aladin.cds.unistra.fr/AladinLite/logos/SIMBAD.svg',
cssStyle: {
width: '3rem',
height: '3rem',
cursor: 'help',
},
action(o) {
window.open('https://simbad.cds.unistra.fr/simbad/')
}
},
content: 'database',
tooltip: {content: 'Click to go to the SIMBAD database', position: {direction: 'bottom'}},
},
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
const simbadHiPS = A.catalogHiPS(OverlayStack.predefinedCats.simbad.url, OverlayStack.predefinedCats.simbad.options);
self.aladin.addCatalog(simbadHiPS);
self.mode = 'stack';
}
},
{
label: 'Gaia DR3',
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
const simbadHiPS = A.catalogHiPS(OverlayStack.predefinedCats.gaia.url, OverlayStack.predefinedCats.gaia.options);
self.aladin.addCatalog(simbadHiPS);
self.mode = 'stack';
}
},
{
label: '2MASS',
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
const simbadHiPS = A.catalogHiPS(OverlayStack.predefinedCats.twomass.url, OverlayStack.predefinedCats.twomass.options);
self.aladin.addCatalog(simbadHiPS);
self.mode = 'stack';
}
},
ContextMenu.fileLoaderItem({
label: 'From a VOTable File',
accept: '.xml,.vot',
action(file) {
let url = URL.createObjectURL(file);
A.catalogFromURL(
url,
{onClick: 'showTable'},
(catalog) => {
self.aladin.addCatalog(catalog)
},
e => alert(e)
);
}
}),
{
label: {
icon: {
url: searchIconImg,
monochrome: true,
tooltip: {content: 'Find a specific catalogue <br /> in our database...', position: { direction: 'top' }},
cssStyle: {
cursor: 'help',
},
},
content: 'More...'
},
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
self.catBox = new CatalogQueryBox(self.aladin);
self.catBox._show({position: self.position});
self.mode = 'search';
}
},
]
},
{
label: {
icon: {
url: Icon.dataURLFromSVG({svg: Icon.SVG_ICONS.MOC}),
size: 'small',
tooltip: {content: 'Define a selection coverage', position: {direction: 'bottom'}},
monochrome: true,
cssStyle: {
cursor: 'pointer',
},
},
content: 'MOC'
},
subMenu: [
ContextMenu.fileLoaderItem({
label: 'FITS File',
accept: '.fits',
action(file) {
let url = URL.createObjectURL(file);
let moc = A.MOCFromURL(
url,
{name: file.name, lineWidth: 3.0},
);
self.aladin.addMOC(moc)
}
}),
{
label: 'From selection',
subMenu: [
{
label: '◌ Circle',
disabled: self.aladin.view.mode !== View.PAN ? {
reason: 'Exit your current mode<br/>(e.g. disable the SIMBAD pointer mode)'
} : false,
action(o) {
o.preventDefault();
o.stopPropagation();
self._hide();
self.aladin.select('circle', c => {
try {
let [ra, dec] = self.aladin.pix2world(c.x, c.y, 'j2000');
let radius = self.aladin.angularDist(c.x, c.y, c.x + c.r, c.y);
// the moc needs a
let moc = A.MOCFromCone(
{ra, dec, radius},
{name: 'cone', lineWidth: 3.0},
);
self.aladin.addMOC(moc)
} catch {
console.error('Circle out of projection. Selection canceled')
}
})
}
},
{
label: '⬚ Rect',
disabled: self.aladin.view.mode !== View.PAN ? {
reason: 'Exit your current mode<br/>(e.g. disable the SIMBAD pointer mode)'
} : false,
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
self.aladin.select('rect', r => {
try {
let [ra1, dec1] = self.aladin.pix2world(r.x, r.y, 'j2000');
let [ra2, dec2] = self.aladin.pix2world(r.x + r.w, r.y, 'j2000');
let [ra3, dec3] = self.aladin.pix2world(r.x + r.w, r.y + r.h, 'j2000');
let [ra4, dec4] = self.aladin.pix2world(r.x, r.y + r.h, 'j2000');
let moc = A.MOCFromPolygon(
{
ra: [ra1, ra2, ra3, ra4],
dec: [dec1, dec2, dec3, dec4]
},
{name: 'rect', lineWidth: 3.0},
);
self.aladin.addMOC(moc)
} catch(_) {
alert('Selection covers a region out of the projection definition domain.');
}
})
}
},
{
label: '⛉ Polygon',
disabled: self.aladin.view.mode !== View.PAN ? {
reason: 'Exit your current mode<br/>(e.g. disable the SIMBAD pointer mode)'
} : false,
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
self.aladin.select('poly', p => {
try {
let ra = []
let dec = []
for (const v of p.vertices) {
let [lon, lat] = self.aladin.pix2world(v.x, v.y, 'j2000');
ra.push(lon)
dec.push(lat)
}
let moc = A.MOCFromPolygon(
{ra, dec},
{name: 'poly', lineWidth: 3.0},
);
self.aladin.addMOC(moc)
} catch(_) {
alert('Selection covers a region out of the projection definition domain.');
}
})
}
},
]
}
]
}
]
}];
for(const overlay of overlays) {
const name = overlay.name;
let cssStyle = {
height: 'fit-content',
};
let showBtn = new ActionButton({
size: 'small',
icon: {
url: overlay.isShowing ? showIconUrl : hideIconUrl,
monochrome: true,
},
cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},
tooltip: {content: overlay.isShowing ? 'Hide' : 'Show', position: {direction: 'bottom'}},
action(e, btn) {
if (overlay.isShowing) {
overlay.hide()
btn.update({icon: {monochrome: true, url: hideIconUrl}, tooltip: {content: 'Show'}});
} else {
overlay.show()
btn.update({icon: {monochrome: true, url: showIconUrl}, tooltip: {content: 'Hide'}});
}
}
});
let deleteBtn = new ActionButton({
icon: {
url: removeIconUrl,
monochrome: true,
},
size: 'small',
cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},
tooltip: {
content: 'Remove',
position: {direction: 'bottom'}
},
action(e) {
self.aladin.removeLayer(overlay)
}
});
let item = Layout.horizontal({
layout: [
this._addOverlayIcon(overlay),
'<div style="background-color: rgba(0, 0, 0, 0.6); padding: 3px; border-radius: 3px; word-break: break-word;">' + name + '</div>',
Layout.horizontal({layout: [showBtn, deleteBtn]})
],
cssStyle: {
textAlign: 'center',
}
});
if(!Utils.hasTouchScreen()) {
layout.push({
label: item,
cssStyle,
hover(e) {
showBtn.el.style.visibility = 'visible'
deleteBtn.el.style.visibility = 'visible'
},
unhover(e) {
showBtn.el.style.visibility = 'hidden'
deleteBtn.el.style.visibility = 'hidden'
},
})
} else {
layout.push({
label: item,
cssStyle
})
}
}
// survey list
let selectedLayer = self.aladin.getSelectedLayer();
/*if (!layers) {
super.attach(layout);
return;
}*/
const defaultLayers = Object.entries(ImageSurvey.cache).sort(function (e1, e2) {
let a = e1[1]
let b = e2[1]
if (!a.order) {
return a.name > b.name ? 1 : -1;
}
return a.maxOrder && a.maxOrder > b.maxOrder ? 1 : -1;
});
for(const layer of layers) {
let backgroundUrl = layer.url + '/preview.jpg';
let cssStyle = {
height: 'fit-content',
};
if (backgroundUrl) {
cssStyle = {
backgroundSize: '100%',
backgroundImage: 'url(' + backgroundUrl + ')',
...cssStyle
}
}
let showBtn = ActionButton.createSmallSizedIconBtn({
icon: {
url: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
monochrome: true,
},
cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},
tooltip: {content: layer.getOpacity() === 0.0 ? 'Show' : 'Hide', position: {direction: 'bottom'}},
action(e, btn) {
e.preventDefault();
e.stopPropagation();
let opacity = layer.getOpacity();
if (opacity === 0.0) {
layer.setOpacity(1.0);
btn.update({icon: {monochrome: true, url: showIconUrl}, tooltip: {content: 'Hide'}});
} else {
layer.setOpacity(0.0);
btn.update({icon: {monochrome: true, url: hideIconUrl}, tooltip: {content: 'Show'}});
}
}
});
let deleteBtn = ActionButton.createSmallSizedIconBtn({
icon: {url: removeIconUrl, monochrome: true},
cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},
disable: layer.layer === 'base',
tooltip: {content: 'Remove', position: {direction: 'bottom'}},
action(e) {
self.aladin.removeImageLayer(layer.layer);
}
});
let editBtn = ActionButton.createSmallSizedIconBtn({
icon: {url: settingsIconUrl, monochrome: true},
cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},
tooltip: {content: 'Settings', position: {direction: 'bottom'}},
action: (e) => {
e.stopPropagation();
e.preventDefault();
self._hide();
//self.aladin.selectLayer(layer.layer);
//self.attach()
self.editBox = new LayerEditBox(self.aladin);
self.editBox.update({layer})
self.editBox._show({position: self.position});
self.mode = 'edit';
}
});
let loadMOCBtn = new TogglerActionButton({
size: 'small',
cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},
icon: {url: Icon.dataURLFromSVG({svg: Icon.SVG_ICONS.MOC}), monochrome: true},
tooltip: {content: 'Add coverage', position: {direction: 'bottom'}},
toggled: (() => {
let overlays = self.aladin.getOverlays();
let found = overlays.find((o) => o.type === "moc" && o.name === layer.name);
return found !== undefined;
})(),
actionOn: (e) => {
let moc = A.MOCFromURL(layer.url + '/Moc.fits', {lineWidth: 3, name: layer.name});
self.aladin.addMOC(moc);
self.mocHiPSUrls[layer.url] = moc;
loadMOCBtn.update({tooltip: {content: 'Remove coverage', position: {direction: 'bottom'}}})
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message: 'Coverage of ' + layer.name + ' loaded',
duration: 2000,
type: 'info'
})
}
},
actionOff: (e) => {
let moc = self.mocHiPSUrls[layer.url];
self.aladin.removeLayer(moc)
delete self.mocHiPSUrls[layer.url];
loadMOCBtn.update({tooltip: {content: 'Add coverage', position: {direction: 'bottom'}}})
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message: 'Coverage of ' + layer.name + ' removed',
duration: 2000,
type: 'info'
})
}
}
});
let layerClassName = 'a' + layer.layer.replace(/[.\/ ]/g, '')
let btns = [showBtn, editBtn];
if (layer.subtype !== 'fits') {
btns.push(loadMOCBtn)
}
btns.push(deleteBtn)
let item = Layout.horizontal({
layout: [
'<div class="' + layerClassName + '" style="background-color: rgba(0, 0, 0, 0.6); line-height: 1rem; padding: 3px; border-radius: 3px; word-break: break-word;' + (selectedLayer === layer.layer ? 'border: 1px solid white;' : '') + '">' + (layer.name) + '</div>',
Layout.horizontal(btns)
],
/*cssStyle: {
display: 'flex',
alignItems: 'center',
listStyle: 'none',
justifyContent: 'space-between',
width: '100%',
}*/
});
let l = {
label: item,
classList: 'surveyItem',
cssStyle,
hover(e) {
showBtn.el.style.visibility = 'visible'
editBtn.el.style.visibility = 'visible'
deleteBtn.el.style.visibility = 'visible'
loadMOCBtn.el.style.visibility = 'visible'
},
unhover(e) {
showBtn.el.style.visibility = 'hidden'
editBtn.el.style.visibility = 'hidden'
deleteBtn.el.style.visibility = 'hidden'
loadMOCBtn.el.style.visibility = 'hidden'
}
};
l.subMenu = [];
l.subMenu.push({
label: {
icon: {
url: searchIconImg,
monochrome: true,
tooltip: {content: 'Find a specific survey <br /> in our database...', position: { direction: 'bottom' }},
cssStyle: {
cursor: 'help',
},
},
content: 'More...'
},
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
self.hipsBox = new HiPSSelectorBox(self.aladin)
self.hipsBox.attach((HiPSId) => {
self.aladin.setOverlayImageLayer(HiPSId, layer.layer);
self.show();
});
self.hipsBox._show({
position: self.position,
})
self.mode = 'hips';
}
})
for(const [id, ll] of defaultLayers) {
backgroundUrl = OverlayStack.previewImagesUrl[ll.name];
if (!backgroundUrl) {
backgroundUrl = ll.url + '/preview.jpg'
}
let cssStyle = {
height: '2.5em',
};
if (backgroundUrl) {
cssStyle = {
backgroundSize: '100%',
backgroundImage: 'url(' + backgroundUrl + ')',
...cssStyle
}
}
l.subMenu.push({
//selected: layer.name === aladin.getBaseImageLayer().name,
label: '<div style="background-color: rgba(0, 0, 0, 0.6); line-height: 1rem; padding: 3px; border-radius: 3px">' + ll.name + '</div>',
cssStyle,
action(e) {
self.aladin.setOverlayImageLayer(id, layer.layer);
},
hover(e, item) {
item.style.filter = 'brightness(1.5)';
},
unhover(e, item) {
item.style.filter = 'brightness(1.0)';
}
})
}
l.action = (o) => {
let oldLayerClassName = 'a' + self.aladin.getSelectedLayer().replace(/[.\/ ]/g, '')
self.el.querySelector('.' + oldLayerClassName).style.removeProperty('border')
self.aladin.selectLayer(layer.layer);
self.el.querySelector('.' + layerClassName).style.border = '1px solid white';
}
layout.push(l);
}
super.attach(layout);
}
_findPreviewImageUrl(layer) {
if (layer instanceof ImageFITS) {
return;
}
if (!layer.creatorDid) {
return;
}
const creatorDid = layer.creatorDid;
for (const key in Stack.previewImagesUrl) {
if (creatorDid.includes(key)) {
return Stack.previewImagesUrl[key];
}
}
// if not found
return layer.url + '/preview.jpg'
}
_addOverlayIcon(overlay) {
var tooltipText;
var svg = '';
if (overlay.type == 'catalog' || overlay.type == 'progressivecat') {
var nbSources = overlay.getSources().length;
tooltipText = nbSources + ' source' + (nbSources > 1 ? 's' : '');
svg = Icon.SVG_ICONS.CATALOG;
}
else if (overlay.type == 'moc') {
tooltipText = 'Coverage: ' + (100 * overlay.skyFraction()).toFixed(2) + ' % of sky';
svg = Icon.SVG_ICONS.MOC;
}
else if (overlay.type == 'overlay') {
svg = Icon.SVG_ICONS.OVERLAY;
}
let tooltip;
if (tooltipText) {
tooltip = { content: tooltipText, position: {direction: 'bottom'} }
}
// retrieve SVG icon, and apply the layer color
return new Icon({
size: 'small',
url: Icon.dataURLFromSVG({svg, color: overlay.color}),
tooltip
});
}
show(options) {
if (this.mode !== 'stack') {
if(this.hipsSelectorBox) {
this.hipsSelectorBox.remove()
}
if(this.catBox) {
this.catBox.remove()
}
if(this.hipsBox) {
this.hipsBox.remove()
}
if(this.editBox) {
this.editBox.remove()
}
}
self.mode = 'stack';
this.attach();
this.position = (options && options.position) || this.position || { anchor: 'center center'};
this.position.aladin = this.aladin;
super.show({
...options,
...{position: this.position},
cssStyle: {
maxWidth: '17rem',
}
})
const innerHeight = this.aladin.aladinDiv.offsetHeight;
this.element().querySelectorAll(".surveyItem")
.forEach((surveyItem) => {
surveyItem.querySelectorAll(".aladin-context-sub-menu")
// skip the first menu
.forEach((subMenu) => {
subMenu.style.display = 'block'
let Y = innerHeight - (subMenu.getBoundingClientRect().y - this.aladin.aladinDiv.getBoundingClientRect().y);
subMenu.style.display = 'none'
subMenu.style.maxHeight = Y + 'px';
subMenu.style.overflowY = 'scroll';
})
})
}
}

View File

@@ -21,6 +21,9 @@ import { Box } from "../Widgets/Box.js";
import { Layout } from "../Layout.js";
import { ActionButton } from "../Widgets/ActionButton.js";
import { ALEvent } from "../../events/ALEvent.js";
import { Icon } from "../Widgets/Icon.js";
import infoIconUrl from '../../../../assets/icons/info.svg';
/******************************************************************************
* Aladin Lite project
*
@@ -60,90 +63,45 @@ import { ALEvent } from "../../events/ALEvent.js";
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import { Input } from "../Widgets/Input.js";
import { Input } from "./../Widgets/Input.js";
export class HiPSSearch extends Input {
static HiPSList = {};
export class Dropdown extends Input {
// constructor
constructor(aladin, options) {
let self;
let layer = options && options.layer;
aladin.view.catalogCanvas.addEventListener('click', (e) => {
self.el.blur();
});
let prevKey = layer.name;
options.options = options.options || [];
super({
name: 'HiPS search',
type: 'text',
classList: ['search'],
name: 'survey',
placeholder: "Survey keywords or url",
autocomplete: {options: Object.keys(HiPSSearch.HiPSList)},
title: layer.name,
actions: {
change(e) {
const key = e.target.value;
if (!key) {
self.update({value: prevKey, title: prevKey});
return;
}
let image;
// A user can put an url
try {
image = new URL(key).href;
} catch(e) {
// Or he can select a HiPS from the list given
let hips = HiPSSearch.HiPSList[key]
//console.log("HIPS", key, hips)
if (hips) {
image = hips.id || hips.url || undefined;
} else {
// Finally if not found, interpret the input text value as the HiPS (e.g. ID)
image = key;
}
}
self.el.blur();
if (image) {
prevKey = key;
aladin.setOverlayImageLayer(image, layer.layer);
}
},
/*input(e) {
let value = e.target.value;
self.update({value, title: value})
}*/
},
value: layer.name,
autocomplete: {options: options.options},
...options
})
this.addClass('aladin-HiPS-search')
this.el.classList.add('search')
self = this;
this.layer = layer;
this._addEventListeners(aladin);
this._addListeners(aladin);
}
setAutocompletionList(options) {
this.update({autocomplete: {options}})
update(options) {
let newOptions = {};
if (options && options.options) {
newOptions['autocomplete'] = {options: options.options};
delete options.options;
}
// add the other input text options
newOptions = {...newOptions, ...options};
super.update(newOptions)
}
_addEventListeners(aladin) {
let self = this;
ALEvent.HIPS_LAYER_ADDED.listenedBy(aladin.aladinDiv, (e) => {
const layer = e.detail.layer;
if (layer.layer === self.layer.layer) {
let value = layer.name
self.update({value, title: value})
}
});
_addListeners(aladin) {
}
};

View File

@@ -106,13 +106,6 @@ export class Layout extends DOMElement {
return layout;
}
static toolbar(options, target, position = "beforeend") {
let layout = new Layout(options, target, position);
layout.addClass('aladin-toolbar');
return layout;
}
/**
* Append an item at the beginning
* @param {DOMElement} item - Represents the structure of the Tabs

View File

@@ -76,10 +76,11 @@ export class Location extends DOMElement {
keydown: (e) => {
e.stopPropagation();
field.removeClass('aladin-unknownObject'); // remove red border
field.removeClass('aladin-not-valid'); // remove red border
field.removeClass('aladin-valid'); // remove red border
if (e.key === 'Enter') {
field.el.blur();
//field.el.blur();
let object = field.get();
@@ -90,12 +91,14 @@ export class Location extends DOMElement {
object,
{
error: function () {
field.addClass('aladin-unknownObject');
field.addClass('aladin-not-valid');
field.update({placeholder: object + ' not found...'})
field.set('');
field.el.focus();
},
success: function() {
field.addClass('aladin-valid');
field.update({placeholder:'Search for an object...', value: object});
}
}
@@ -154,7 +157,7 @@ export class Location extends DOMElement {
}
let [lon, lat] = lonlat;
self.field.el.blur()
self.update({
lon, lat,
frame: aladin.view.cooFrame,
@@ -162,6 +165,10 @@ export class Location extends DOMElement {
}, aladin);
}
if(param.state.dragging) {
self.field.el.blur()
}
if (param.type === 'mousemove' && param.state.dragging === false) {
if (focused) {
return;
@@ -177,6 +184,7 @@ export class Location extends DOMElement {
});
ALEvent.POSITION_CHANGED.listenedBy(aladin.aladinDiv, function (e) {
self.update({
lon: e.detail.lon,
lat: e.detail.lat,
@@ -220,7 +228,8 @@ export class Location extends DOMElement {
else {
self.field.set(coo.format('d/'))
}
self.field.removeClass('aladin-unknownObject');
self.field.removeClass('aladin-not-valid');
self.field.removeClass('aladin-valid');
self.field.element().style.color = options.isViewCenter ? aladin.getReticle().getColor() : 'white';
//self.field.el.blur()

View File

@@ -167,6 +167,10 @@ export class ActionButton extends DOMElement {
super._show();
}
click() {
this.el.click()
}
static createIconBtn(opt, target, position = 'beforeend') {
let btn = new ActionButton({...opt, size: 'medium'}, target, position);

View File

@@ -50,11 +50,10 @@ export class Box extends DOMElement {
//el.style.display = "initial";
super(el, options);
this._show();
this.addClass('aladin-dark-theme')
this.attachTo(target, position);
this._show();
this.addClass('aladin-dark-theme')
}
_show(options) {
@@ -67,7 +66,8 @@ export class Box extends DOMElement {
let self = this;
let close = this.options.close === false ? false : true;
let close = this.options.close === false ? false : true;
let draggable = false;
if (close) {
new ActionButton({
size: 'small',
@@ -86,6 +86,10 @@ export class Box extends DOMElement {
}, this.el);
}
if (this.options.onDragged) {
draggable = true;
}
// Check for the title
if (this.options.header) {
let header = this.options.header;
@@ -99,6 +103,10 @@ export class Box extends DOMElement {
let draggableEl;
if (header.draggable) {
draggable = true;
}
if (draggable) {
draggableEl = new ActionButton({
icon: {
url: moveIconImg,
@@ -111,18 +119,13 @@ export class Box extends DOMElement {
},
action(e) {}
});
dragElement(draggableEl.element(), this.el)
dragElement(titleEl, this.el)
titleEl.style.cursor = 'move'
}
Layout.horizontal({
cssStyle: {
justifyContent: 'space-between',
},
layout: [draggableEl, titleEl]
}, this.el);
let headerEl = Layout.horizontal([draggableEl, titleEl], this.el);
if (draggable) {
dragElement(headerEl.element(), this.el, this.options.onDragged);
headerEl.element().style.cursor = 'move';
}
let separatorEl = document.createElement('div')
separatorEl.classList.add("aladin-box-separator");
@@ -135,12 +138,11 @@ export class Box extends DOMElement {
if (this.options.content) {
let content = this.options.content
if (Array.isArray(content)) {
this.appendContent(new Layout({layout: content}));
} else {
//if (Array.isArray(content)) {
this.appendContent(content);
}
//} else {
// this.appendContent(content);
//}
}
if (this.options.position) {
@@ -156,10 +158,10 @@ export class Box extends DOMElement {
}
// Heavily inspired from https://www.w3schools.com/howto/howto_js_draggable.asp
function dragElement(triggerElt, elmnt) {
function dragElement(triggerElt, elmnt, onDragged) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
// otherwise, move the DIV from anywhere inside the DIV:
var t, l;
triggerElt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
@@ -171,6 +173,10 @@ function dragElement(triggerElt, elmnt) {
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
if (onDragged) {
onDragged();
}
}
function elementDrag(e) {
@@ -181,14 +187,37 @@ function dragElement(triggerElt, elmnt) {
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
t = elmnt.offsetTop - pos2
l = elmnt.offsetLeft - pos1
elmnt.style.top = t + "px";
elmnt.style.left = l + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
var r = elmnt.getBoundingClientRect();
if (t < r.height / 2) {
elmnt.style.top = r.height / 2 + "px";
}
if (l < r.width / 2) {
elmnt.style.left = r.width / 2 + "px";
}
const aladinDiv = elmnt.closest('.aladin-container');
if (l + r.width / 2 > aladinDiv.offsetWidth) {
elmnt.style.left = (aladinDiv.offsetWidth - r.width / 2) + "px";
}
if (t + r.height / 2 > aladinDiv.offsetHeight) {
elmnt.style.top = (aladinDiv.offsetHeight - r.height / 2) + "px";
}
}
}

View File

@@ -53,9 +53,13 @@ Exemple of layout object
export class Form extends DOMElement {
constructor(options, target, position = "beforeend") {
let el = document.createElement('form');
el.onsubmit = (e) => {
e.preventDefault();
};
el.className = "aladin-form";
super(el, options);
this.attachTo(target, position)
this._show()
@@ -67,7 +71,7 @@ export class Form extends DOMElement {
let layout = [];
if (this.options && this.options.subInputs) {
this.options.subInputs.forEach(subInput => {
layout.push(Form._createInput(subInput))
layout.push(this._createInput(subInput))
});
}
@@ -87,21 +91,27 @@ export class Form extends DOMElement {
);
}
this.appendContent(new Layout(layout))
this.appendContent(Layout.vertical(layout))
super._show();
}
static _createInput(layout) {
if (!layout.subInputs) {
let input = new Input(layout);
_createInput(layout) {
if (layout instanceof DOMElement || !layout.subInputs) {
let input;
let label = document.createElement('label');
if (layout.labelContent) {
DOMElement.appendTo(layout.labelContent, label);
if (layout instanceof DOMElement) {
input = layout;
label.textContent = input.options.label;
} else {
label.textContent = layout.label;
input = new Input(layout);
if (layout.labelContent) {
DOMElement.appendTo(layout.labelContent, label);
} else {
label.textContent = layout.label;
}
}
label.for = input.el.id;
let item = new Layout([label, input]);
@@ -115,7 +125,7 @@ export class Form extends DOMElement {
}
layout.subInputs.map((subInput) => {
let input = Form._createInput(subInput)
let input = this._createInput(subInput)
groupLayout.push(input)
});

View File

@@ -103,9 +103,6 @@ export class Icon extends DOMElement {
if (this.options.url) {
let img = document.createElement('img');
img.src = this.options.url;
img.style.objectFit = 'contain';
img.style.verticalAlign = 'middle';
img.style.width = '100%';
this.el.appendChild(img);
}
@@ -156,6 +153,9 @@ export class Icon extends DOMElement {
elt.querySelector('svg').setAttribute('width', size);
elt.querySelector('svg').setAttribute('height', size);
elt.style.width = size;
elt.style.height = size;
return elt.innerHTML;
};

View File

@@ -20,6 +20,7 @@
import { DOMElement } from "./Widget";
import { Tooltip } from "./Tooltip";
import { Utils } from "../../Utils";
/******************************************************************************
* Aladin Lite project
*
@@ -59,9 +60,9 @@ export class Input extends DOMElement {
super(el, options);
this._show()
this.attachTo(target, position)
this.target = target;
this._show()
}
_show() {
@@ -105,7 +106,95 @@ export class Input extends DOMElement {
this.el.step = "any";
}
let self = this;
function logPositionMinMax(value, minp, maxp) {
// position will be between 1 / 3600 and 1.0
var minv = Math.log(minp);
var maxv = Math.log(maxp);
// calculate adjustment factor
var scale = (maxv-minv) / (maxp-minp);
console.log('value', value)
return (Math.log(value)-minv) / scale + minp;
}
function logPosition(value) {
// position will be between 1 / 3600 and 1.0
var minp = self.options.min; // 1 arcsec
var maxp = self.options.max; // 1 deg
var minv = Math.log(self.options.min);
var maxv = Math.log(self.options.max);
// calculate adjustment factor
var scale = (maxv-minv) / (maxp-minp);
console.log(minv, maxv)
return (Math.log(value)-minv) / scale + minp;
}
function logSliderMinMax(position, minp, maxp) {
var minv = Math.log(minp);
var maxv = Math.log(maxp);
// calculate adjustment factor
var scale = (maxv-minv) / (maxp-minp);
return Math.exp(minv + scale*(position-minp));
}
function logSlider(position) {
// position will be between 1 / 3600 and 1.0
var minp = self.options.min; // 1 arcsec
var maxp = self.options.max; // 1 deg
var minv = Math.log(self.options.min);
var maxv = Math.log(self.options.max);
// calculate adjustment factor
var scale = (maxv-minv) / (maxp-minp);
return Math.exp(minv + scale*(position-minp));
}
if (this.options.type === "range") {
if (this.options.reversed === true) {
this.addClass('aladin-reversed');
}
if (this.options.stretch) {
let stretch = this.options.stretch || 'linear';
if (stretch === 'log') {
// Refers to this StackOverflow post: https://stackoverflow.com/questions/846221/logarithmic-slider
if (this.options.ticks) {
this.options.ticks = this.options.ticks.map((t) => logPosition(t));
}
if (this.options.change) {
let change = this.options.change;
this.options.change = (e) => {
const value = logSlider(e.target.value)
/*let p = 100 * (e.target.value - this.options.min) / (this.options.max - this.options.min);
if (this.options.reversed === true) {
p = 100 - p;
}
this.el.style.background = 'linear-gradient(to right, lightgreen ' + p + '%, #bababa ' + p + '%)';
*/
change(e, this, value);
};
}
}
}
if (this.options.ticks) {
this.options.autocomplete = {options: this.options.ticks};
delete this.options.ticks;
@@ -119,21 +208,55 @@ export class Input extends DOMElement {
if (this.options.autocomplete) {
const autocomplete = this.options.autocomplete
if (autocomplete instanceof Object && autocomplete !== null) {
let datalist = document.createElement('datalist');
let datalist = null;
if (this.el.parentNode) {
datalist = this.el.parentNode.querySelector('#' + 'ticks-' + this.options.name);
}
if (datalist) {
// it has been found, remove what's inside it
datalist.innerHTML = "";
} else {
// it has not been found, then create it
if (this.options.type === 'range') {
datalist = document.createElement('datalist');
} else {
datalist = document.createElement('datalist');
}
datalist.id = 'ticks-' + this.options.name;
if (this.options.type === "range")
datalist.classList.add('aladin-input-range-datalist')
// and insert it into the dom
this.el.appendChild(datalist);
this.el.setAttribute('list', datalist.id);
}
autocomplete.options.forEach((o) => {
let option = document.createElement('option');
option.value = o;
datalist.appendChild(option);
})
let optionEl;
if (this.options.type === 'range') {
optionEl = document.createElement('option');
let p = (o - this.options.min) / (this.options.max - this.options.min);
optionEl.value = o;
datalist.id = 'ticks-' + this.options.name;
this.el.setAttribute('list', datalist.id);
if (this.el.querySelector('#' + datalist.id)) {
this.el.querySelector('#' + datalist.id).remove()
}
this.el.appendChild(datalist);
const lerp = (x, min, max) => {
return x * max + (1 - x) * min;
};
if (this.options.reversed) {
p = 1 - p;
}
optionEl.style.left = 'calc(' + 100.0 * p + '% + ' + lerp(p, 0.5, -0.5) + 'rem)';
} else {
optionEl = document.createElement('option');
optionEl.value = o;
}
datalist.appendChild(optionEl);
})
this.el.autocomplete = 'off';
} else {
@@ -163,13 +286,17 @@ export class Input extends DOMElement {
if (this.options.change) {
if (this.options.type === 'color' || this.options.type === 'range' || this.options.type === "text") {
this.el.removeEventListener('input', this.action);
this.action = (e) => {
this.options.change(e, this);
};
this.el.addEventListener('input', this.action);
} else {
this.el.removeEventListener('change', this.action);
this.action = this.options.change;
this.el.addEventListener('change', this.action);
}
}
@@ -227,9 +354,16 @@ export class Input extends DOMElement {
super._show()
}
/*setPlaceholder(placeholder) {
this.el.placeholder = placeholder;
}*/
attachTo(target, position = 'beforeend') {
super.attachTo(target, position);
if (this.options.type === "range") {
// Dirty workaround for plotting the slider ticks
// The input slider must have a parent so that
// its datalist can be put into the DOM
this._show()
}
}
update(options) {
// if no options given, use the previous one set

View File

@@ -81,6 +81,7 @@ export class Table extends DOMElement {
let val = row.data[key] || '--';
tdEl.innerHTML = val;
tdEl.classList.add("aladin-text-td-container");
tdEl.title = val;
}
trEl.appendChild(tdEl);

View File

@@ -136,15 +136,17 @@ export class Tooltip extends DOMElement {
}
if (this.options.content) {
let content = this.options.content;
if (content instanceof DOMElement) {
content.attachTo(tooltipEl)
} else if (content instanceof Element) {
tooltipEl.insertAdjacentElement('beforeend', content);
} else {
let wrapEl = document.createElement('div');
wrapEl.innerHTML = content;
tooltipEl.insertAdjacentElement('beforeend', wrapEl);
let content = [].concat(this.options.content);
for (var c of content) {
if (c instanceof DOMElement) {
c.attachTo(tooltipEl)
} else if (c instanceof Element) {
tooltipEl.insertAdjacentElement('beforeend', c);
} else {
let wrapEl = document.createElement('div');
wrapEl.innerHTML = c;
tooltipEl.insertAdjacentElement('beforeend', wrapEl);
}
}
}

View File

@@ -2,14 +2,21 @@
// Class Coo
//=================================
import { Format } from "./coo";
/**
* Constructor
* @param angle angle (precision in degrees)
* @param prec precision
* Creates an angle of the Aladin interactive sky atlas.
* @class
* @constructs Angle
* @param {number} angle - angle in degrees
* @param {number} prec - precision
* (8: 1/1000th sec, 7: 1/100th sec, 6: 1/10th sec, 5: sec, 4: 1/10th min, 3: min, 2: 1/10th deg, 1: deg
*/
export let Angle = function(angle, prec) {
this.angle = angle;
if (prec === undefined || prec === null) {
prec = 0;
}
this.prec = prec;
};
@@ -43,28 +50,66 @@ Angle.prototype = {
return Format.toDecimal(fov, this.prec) + suffix;
},
/**
* @memberof Angle
*
* @param {string} str - A string in the form [<deg>°<minutes>'<seconds>"]. [hms] form is not supported
* @returns {boolean} - Whether the string has been successfully parsed
*/
parse: function(str) {
// check for degrees
let idxUnit;
idxUnit = str.indexOf('°');
if (idxUnit > 0) {
this.angle = +str.substring(0, idxUnit)
return true;
let idx = str.indexOf('°');
let angleDeg = NaN;
if (idx > 0) {
const deg = parseFloat(str.substring(0, idx));
if (!Number.isFinite(deg)) {
return false
}
angleDeg = deg;
str = str.substring(idx + 1)
}
idxUnit = str.indexOf('\'');
if (idxUnit > 0) {
this.angle = (+str.substring(0, idxUnit)) / 60.0
return true;
idx = str.indexOf('\'');
if (idx > 0) {
const minutes = parseFloat(str.substring(0, idx))
if (!Number.isFinite(minutes)) {
return false
}
if (!Number.isFinite(angleDeg)) {
angleDeg = 0;
}
angleDeg += minutes / 60.0
str = str.substring(idx + 1);
}
idxUnit = str.indexOf('"');
if (idxUnit > 0) {
this.angle = (+str.substring(0, idxUnit)) / 3600.0
return true;
idx = str.indexOf('"');
if (idx > 0) {
const seconds = parseFloat(str.substring(0, idx))
if (!Number.isFinite(seconds)) {
return false;
}
if (!Number.isFinite(angleDeg)) {
angleDeg = 0;
}
angleDeg += seconds / 3600.0
}
return false
if (Number.isFinite(angleDeg)) {
this.angle = angleDeg;
return true;
} else {
return false
}
},
degrees: function() {

View File

@@ -34,6 +34,10 @@ import { Utils } from './../Utils';
import { ActionButton } from "../gui/Widgets/ActionButton.js";
import { Catalog } from "../Catalog.js";
import { ServiceQueryBox } from "../gui/Box/ServiceQueryBox.js";
import { Input } from "../gui/Widgets/Input.js";
import { Layout } from "../gui/Layout.js";
import { HiPSProperties } from "../HiPSProperties.js";
import A from "../A.js";
export let Datalink = (function() {
@@ -68,11 +72,25 @@ export let Datalink = (function() {
})
let self = this;
let datalinkTable = {
'name': 'Datalink:' + url,
'color': 'purple',
'rows': measures,
'fields': fields,
'showCallback': {
name: 'Datalink:' + url,
color: 'purple',
rows: measures,
fields,
showCallback: {
content_type: (data) => {
let contentType = data['content_type'];
if (contentType && contentType.includes('datalink')) {
return new ActionButton({
size: 'small',
content: '🔗',
tooltip: {content: 'Datalink VOTable', aladin: aladinInstance, global: true},
action(e) {}
}).element();
} else {
return contentType;
}
},
'service_def': (data) => {
const serviceName = data['service_def'];
@@ -80,7 +98,7 @@ export let Datalink = (function() {
return new ActionButton({
size: 'small',
content: '📡',
tooltip: {content: 'Open the cutout service form', position: {direction: 'top'}},
tooltip: {global: true, aladin: aladinInstance, content: 'Open the cutout service form'},
action(e) {
if (self.serviceQueryBox) {
self.serviceQueryBox.remove()
@@ -137,8 +155,90 @@ export let Datalink = (function() {
break;
case 'application/hips':
// Clic on a HiPS
let survey = aladinInstance.newImageSurvey(url);
aladinInstance.setOverlayImageLayer(survey, Utils.uuidv4())
let layer = Utils.uuidv4();
if (contentQualifier === "cube") {
let cubeOnTheFlyUrl = url + '/';
HiPSProperties.fetchFromUrl(cubeOnTheFlyUrl)
.then(properties => {
let numSlices = +properties.hips_cube_depth;
let idxSlice = +properties.hips_cube_firstframe;
let updateSlice = () => {
let colorCfg = aladinInstance.getOverlayImageLayer(layer).getColorCfg();
let hips = aladinInstance.setOverlayImageLayer(cubeOnTheFlyUrl + idxSlice, layer)
hips.setColorCfg(colorCfg)
slicer.update({
value: idxSlice,
tooltip: {content: (idxSlice + 1) + '/' + numSlices, position: {direction: 'bottom'}},
})
};
let slicer = Input.slider({
label: "Slice",
name: "cube slicer",
ticks: [idxSlice],
tooltip: {content: (idxSlice + 1) + '/' + numSlices , position: {direction: 'bottom'}},
min: 0,
max: numSlices - 1,
value: idxSlice,
actions: {
change: (e) => {
idxSlice = Math.round(e.target.value);
updateSlice();
},
input: (e) => {
idxSlice = Math.round(e.target.value);
slicer.update({
value: idxSlice,
tooltip: {content: (idxSlice + 1) + '/' + numSlices, position: {direction: 'bottom'}},
})
}
},
cssStyle: {
width: '300px'
}
});
let prevBtn = A.button({
size: 'small',
content: '<',
action(o) {
idxSlice = Math.max(idxSlice - 1, 0);
updateSlice()
}
})
let nextBtn = A.button({
size: 'small',
content: '>',
action(o) {
idxSlice = Math.min(idxSlice + 1, numSlices - 1);
updateSlice()
}
})
let cubeDisplayer = A.box({
close: true,
header: {
title: 'HiPS cube player',
draggable: true,
},
content: Layout.horizontal([prevBtn, nextBtn, slicer]),
position: {anchor: 'center top'},
});
aladinInstance.addUI(cubeDisplayer)
aladinInstance.setOverlayImageLayer(cubeOnTheFlyUrl + idxSlice, layer)
})
} else {
let survey = aladinInstance.newImageSurvey(url);
aladinInstance.setOverlayImageLayer(survey, layer)
}
break;
// Any generic FITS file
case 'application/fits':
@@ -159,7 +259,7 @@ export let Datalink = (function() {
break;
default:
// When all has been done, download what's under the link
//Utils.download(url);
Utils.openNewTab(url);
break;
}
});

View File

@@ -191,7 +191,7 @@
let accessUrlEl = document.createElement('div');
try {
let _ = new URL(url);
accessUrlEl.classList.add('aladin-href-td-container');
accessUrlEl.classList.add('aladin-href-link');
accessUrlEl.innerHTML = '<a href=' + url + ' target="_blank">' + url + '</a>';
@@ -245,7 +245,7 @@
return new ActionButton({
size: 'small',
content: '🔗',
tooltip: {content: accessFormat, position: {direction: 'left'}},
tooltip: {content: 'Datalink VOTable', aladin: aladinInstance, global: true},
action(e) {}
}).element();
} else {

View File

@@ -458,19 +458,19 @@ export class VOTable {
subInputs: [{
name: 'ra',
label: 'ra[' + unit + ']',
type: 'number',
type: 'text',
value: values && values[0],
},
{
name: 'dec',
label: 'dec[' + unit + ']',
type: 'number',
type: 'text',
value: values && values[1],
},
{
name: 'rad',
label: 'rad[' + unit + ']',
type: 'number',
type: 'text',
value: values && values[2],
}]
};