Compare commits

...

5 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
44 changed files with 2652 additions and 1472 deletions

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

@@ -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

@@ -14,7 +14,7 @@
fullScreen: true,
target: "centaurus A",
fov: 10,
projection: 'AIT',
projection: 'TAN',
showContextMenu: true,
showSimbadPointerControl: true,
expandLayersControl: true,

View File

@@ -12,7 +12,7 @@
aladin = A.aladin("#aladin-lite-div", {
fullScreen: true,
target: "m51",
fov: 90,
fov: 15,
projection: "AIT",
showContextMenu: true,
showShareControl: true,
@@ -73,11 +73,11 @@ aladin = A.aladin("#aladin-lite-div", {
],
});
aladin.setImageLayer(A.imageHiPS("astron.nl/P/lotss_dr2_high"));
aladin.setImageLayer(A.imageHiPS("P/DSS2/color"));
aladin.addCatalog(
A.catalogFromSKAORucio("m51", 10, {
onClick: "showTable",
A.catalogFromSKAORucio("m51", 15, {
onClick: 'showTable',
hoverColor: "yellow",
})
);

View File

@@ -17,14 +17,13 @@
aladin.on("zoomChanged", () => {
console.log("zoomChanged")
})
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.4.0-beta",
"version": "3.4.1-beta",
"description": "An astronomical HiPS visualizer in the browser",
"author": "Thomas Boch and Matthieu Baumann",
"license": "GPL-3",

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

@@ -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;
@@ -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,16 +395,19 @@ canvas {
height: 1.7rem;
}
.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 {
@@ -436,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;
@@ -479,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 {
@@ -498,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;
@@ -510,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 {
@@ -886,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 */
@@ -900,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;
@@ -1096,17 +1096,23 @@ 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 {
@@ -1126,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 {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,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -33,13 +33,13 @@ export let HiPSCache = (function () {
HiPSCache.append = function (key, image) {
HiPSCache.cache[key] = image;
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(document.body);
ALEvent.HIPS_CACHE_UPDATED.dispatchedTo(document.body);
};
HiPSCache.delete = function (key) {
delete HiPSCache.cache[key];
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(document.body);
ALEvent.HIPS_CACHE_UPDATED.dispatchedTo(document.body);
};
HiPSCache.get = function (key) {

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

@@ -64,7 +64,6 @@ export let ImageFITS = (function () {
// Name of the layer
this.layer = null;
this.added = false;
this.subtype = "fits";
// Set it to a default value
this.url = url;
this.id = url;
@@ -88,13 +87,6 @@ export let ImageFITS = (function () {
}
ImageFITS.prototype._saveInCache = function () {
/*ImageHiPS.cache[this.id] = this;
// Tell that the HiPS List has been updated
if (this.view) {
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(this.view.aladin.aladinDiv);
}*/
HiPSCache.append(this.id, this);
};
@@ -261,10 +253,6 @@ export let ImageFITS = (function () {
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

View File

@@ -125,15 +125,6 @@ PropertyParser.bitpix = function (properties) {
return bitpix;
};
PropertyParser.dataproductSubtype = function (properties) {
let dataproductSubtype =
(properties && properties.dataproduct_subtype) || "color";
dataproductSubtype = dataproductSubtype
.split(" ")
.map((subtype) => subtype.toLowerCase());
return dataproductSubtype;
};
PropertyParser.isPlanetaryBody = function (properties) {
return properties && properties.hips_body !== undefined;
};
@@ -142,6 +133,8 @@ PropertyParser.isPlanetaryBody = function (properties) {
* @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.
@@ -180,13 +173,12 @@ export let ImageHiPS = (function () {
// Unique identifier for a survey
this.id = id;
this.name = (options && options.name) || undefined;
this.subtype = "survey";
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
@@ -194,6 +186,8 @@ export let ImageHiPS = (function () {
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);
}
@@ -296,12 +290,8 @@ export let ImageHiPS = (function () {
}
})
.catch((e) => {
//alert(e);
console.error(self);
console.error(e);
// the survey has been added so we remove it from the stack
//self.view.removeImageLayer(self.layer)
//throw e;
});
}
@@ -361,6 +351,7 @@ export let ImageHiPS = (function () {
}
self.name = self.name || self.id || self.url;
self.name = self.name.replace(/ +/g, ' ');
self.creatorDid = self.creatorDid || self.id || self.url;
@@ -460,7 +451,7 @@ export let ImageHiPS = (function () {
self._saveInCache();
return self;
})();
})()
};
ImageHiPS.prototype._saveInCache = function () {
@@ -472,10 +463,13 @@ export let ImageHiPS = (function () {
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,
};
@@ -490,21 +484,6 @@ export let ImageHiPS = (function () {
// append new important infos from the properties queried
...surveyOpt,
});
/*ImageHiPS.cache[self.id] = {
// Erase by the cache already put values which is considered
// as the ground truth
...ImageHiPS.cache[self.id],
// append new important infos from the properties queried
...surveyOpt,
}*/
//console.log('new CACHE', ImageHiPS.cache, self.id, surveyOpt, ImageHiPS.cache[self.id], ImageHiPS.cache["CSIRO/P/RACS/mid/I"])
// Tell that the HiPS List has been updated
/*if (this.view) {
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(this.view.aladin.aladinDiv);
}*/
};
/**
@@ -831,7 +810,14 @@ export let ImageHiPS = (function () {
},
});
return Promise.resolve(this);
return Promise.resolve(this)
.then((hips) => {
if (hips.successCallback) {
hips.successCallback(hips)
}
return hips
});
};
// @api

View File

@@ -51,19 +51,46 @@ 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",

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

@@ -126,11 +126,15 @@ export let View = (function () {
if (typeof posChangedFn === 'function') {
var pos = this.aladin.pix2world(this.width / 2, this.height / 2, 'icrs');
if (pos !== undefined) {
posChangedFn({
ra: pos[0],
dec: pos[1],
dragging: true
});
try {
posChangedFn({
ra: pos[0],
dec: pos[1],
dragging: true
});
} catch(e) {
console.error(e)
}
}
}
},
@@ -258,14 +262,9 @@ export let View = (function () {
self.fixLayoutDimensions();
})
} else {*/
let resizeLayout = () => {
self.fixLayoutDimensions();
}
this.resizeObserver = new ResizeObserver(() => {
//clearTimeout(doit);
//doit = setTimeout(resizeLayout, 100);
resizeLayout();
self.fixLayoutDimensions();
});
self.resizeObserver.observe(this.aladinDiv)
@@ -1646,6 +1645,10 @@ export let View = (function () {
// remove it from the cache
HiPSCache.delete(imageLayer.id)
if (imageLayer.errorCallback) {
imageLayer.errorCallback(e);
}
throw e;
})
.finally(() => {
@@ -1742,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]);
}
@@ -1931,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

@@ -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

@@ -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')

View File

@@ -41,18 +41,18 @@ import addIconUrl from "../../../../assets/icons/plus.svg";
import hideIconUrl from "../../../../assets/icons/hide.svg";
import removeIconUrl from "../../../../assets/icons/remove.svg";
import settingsIconUrl from "../../../../assets/icons/settings.svg";
import filterOnUrl from "../../../../assets/icons/filter-on.svg";
import filterOffUrl from "../../../../assets/icons/filter-off.svg";
import searchIconImg from "../../../../assets/icons/search.svg";
import { TogglerActionButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { ImageHiPS } from "../../ImageHiPS.js";
import { Box } from "../Widgets/Box.js";
import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
import { Input } from "../Widgets/Input.js";
import { ImageFITS } from "../../ImageFITS.js";
import { HiPSCache } from "../../DefaultHiPSCache.js";
import { HiPSBrowserBox } from "./HiPSBrowserBox.js";
import { ImageHiPS } from "../../ImageHiPS.js";
export class OverlayStackBox extends Box {
/*static previewImagesUrl = {
@@ -138,24 +138,6 @@ export class OverlayStackBox extends Box {
this.aladin = aladin;
this.filterHiPSOn = false;
this.filterCallback = (HiPS) => {
if (!HiPS.regime) {
return false;
}
if (
this.filterParams.regime &&
HiPS.regime &&
this.filterParams.regime.toLowerCase() !==
HiPS.regime.toLowerCase()
) {
return false;
}
return true;
};
this.mode = "stack";
this._addListeners();
@@ -526,10 +508,10 @@ export class OverlayStackBox extends Box {
{
label: {
icon: {
url: searchIconUrl,
url: addIconUrl,
monochrome: true,
tooltip: {
content: "From our database...",
content: "Add a new layer",
position: { direction: "right" },
},
cssStyle: {
@@ -544,23 +526,50 @@ export class OverlayStackBox extends Box {
/*self._hide();
self.hipsSelectorBox = new HiPSSelectorBox(self.aladin);
// attach a callback
self.hipsSelectorBox.attach(
(HiPSId) => {
let name = Utils.uuidv4()
self.aladin.setOverlayImageLayer(HiPSId, name)
self.hipsSelectorBox = new HiPSSelectorBox(self.aladin);
// attach a callback
self.hipsSelectorBox.attach(
(HiPSId) => {
let name = Utils.uuidv4()
self.aladin.setOverlayImageLayer(HiPSId, name)
self.show();
}
);
self.show();
}
);
self.hipsSelectorBox._show({
position: self.position,
});*/
self.hipsSelectorBox._show({
position: self.position,
});*/
self.aladin.addNewImageLayer();
},
},
{
label: {
icon: {
url: searchIconUrl,
monochrome: true,
tooltip: {
content: "From our database...",
position: { direction: "right" },
},
cssStyle: {
cursor: "help",
},
},
content: "Browse HiPS",
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
if (!self.hipsBrowser)
self.hipsBrowser = new HiPSBrowserBox(aladin);
self.hipsBrowser._show({position: {
anchor: 'center center'
}});
},
},
ContextMenu.fileLoaderItem({
label: "FITS image file",
accept: ".fits",
@@ -595,97 +604,6 @@ export class OverlayStackBox extends Box {
this.aladin
);
this.filterParams = {
regime: "Optical",
};
this.filterBox = new Box(
{
close: false,
content: [
Input.select({
name: "regime",
value: "Optical",
options: [
"Optical",
"UV",
"Radio",
"Infrared",
"X-ray",
"Gamma-ray",
],
change() {
self.filterParams["regime"] = this.value;
if (self.filterHiPSOn) {
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(
self.aladin.aladinDiv
);
}
},
tooltip: {
content: "Observation regime",
position: { direction: "right" },
},
}),
],
},
self.aladin.aladinDiv
);
this.filterBox._hide();
this.filterEnabler = Input.checkbox({
name: "filter-enabler",
checked: this.filterHiPSOn,
tooltip: {
content: self.filterHiPSOn ? "Filtering on" : "Filtering off",
},
click(e) {
self.filterHiPSOn = e.target.checked;
self.filterBtn.update({
icon: {
url: self.filterHiPSOn ? filterOnUrl : filterOffUrl,
monochrome: true,
},
});
self.filterEnabler.update({
tooltip: {
content: self.filterHiPSOn
? "Filtering on"
: "Filtering off",
},
checked: self.filterHiPSOn,
});
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(self.aladin.aladinDiv);
},
});
this.filterBtn = new TogglerActionButton({
icon: {
url: this.filterHiPSOn ? filterOnUrl : 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: self.filterBtn,
direction: "right",
aladin: self.aladin,
},
});
},
actionOff: (e) => {
self.filterBox._hide();
},
});
this.update({ content: this.createLayout() });
}
@@ -784,14 +702,15 @@ export class OverlayStackBox extends Box {
updateOverlayList();
// Add a listener for HiPS list changes
ALEvent.HIPS_LIST_UPDATED.listenedBy(document.body, () => {
ALEvent.HIPS_CACHE_UPDATED.listenedBy(document.body, () => {
self.cachedHiPS = {};
for (var key in HiPSCache.cache) {
let HiPS = HiPSCache.cache[key];
self.cachedHiPS[HiPS.name] = HiPS;
if (HiPS.name) {
self.cachedHiPS[HiPS.name.toString()] = HiPS;
}
}
// Update the options of the selector
@@ -800,36 +719,9 @@ export class OverlayStackBox extends Box {
for (var key in self.HiPSui) {
let hips = self.HiPSui[key];
hips.HiPSSelector.update({options});
hips.HiPSSelector.update({value: hips.HiPSSelector.options.value, options});
}
});
/*ALEvent.HIPS_LIST_UPDATED.listenedBy(this.aladin.aladinDiv, () => {
// Recompute the autocompletion as the cache has changed
HiPSSearch.HiPSList = {};
for (var key in ImageHiPS.cache) {
let HiPS = ImageHiPS.cache[key];
// apply filtering
if (
!self.filterHiPSOn ||
(self.filterHiPSOn && !self.filterCallback) ||
(self.filterHiPSOn &&
self.filterCallback &&
self.filterCallback(HiPS))
) {
// search with the name or id
HiPSSearch.HiPSList[HiPS.name] = HiPS;
}
}
let keys = Object.keys(HiPSSearch.HiPSList);
// Change the autocomplete of all the search input text
for (var key in this.HiPSui) {
let hips = this.HiPSui[key];
hips.searchInput.setAutocompletionList(keys);
}
});*/
}
_hide() {
@@ -841,14 +733,14 @@ export class OverlayStackBox extends Box {
}
}
/*if (this.hipsBrowser) {
this.hipsBrowser._hide();
}*/
if (this.catBox) {
this.catBox._hide();
}
if (this.filterBtn && this.filterBtn.toggled) {
this.filterBtn.toggle();
}
if (this.addOverlayBtn) this.addOverlayBtn.hideMenu();
if (this.addHiPSBtn) this.addHiPSBtn.hideMenu();
@@ -875,7 +767,7 @@ export class OverlayStackBox extends Box {
);
layout = layout.concat(this._createSurveysList());
return new Layout({ layout, classList: ["content"] });
return Layout.vertical({ layout, classList: ["content"] });
}
_createOverlaysList() {
@@ -984,7 +876,7 @@ export class OverlayStackBox extends Box {
_createSurveysList() {
let self = this;
const layers = Array.from(self.aladin.getImageOverlays())
const layers = Array.from(self.aladin.getStackLayers())
.reverse()
.map((name) => {
let overlay = self.aladin.getOverlayImageLayer(name);
@@ -1103,8 +995,8 @@ export class OverlayStackBox extends Box {
);
return found !== undefined;
})(),
action: (e, btn) => {
if (!btn.options.toggled) {
action: (e) => {
if (!loadMOCBtn.options.toggled) {
// load the moc
let moc = A.MOCFromURL(
layer.url + "/Moc.fits",
@@ -1123,7 +1015,7 @@ export class OverlayStackBox extends Box {
});
}
btn.update({
loadMOCBtn.update({
toggled: true,
tooltip: {
content: "Remove coverage",
@@ -1149,7 +1041,7 @@ export class OverlayStackBox extends Box {
});
}
btn.update({
loadMOCBtn.update({
toggled: false,
tooltip: {
content: "Add coverage",
@@ -1162,7 +1054,7 @@ export class OverlayStackBox extends Box {
let btns = [showBtn, settingsBtn];
if (layer.subtype !== "fits") {
if (!(layer instanceof ImageFITS)) {
btns.push(loadMOCBtn);
}
btns.push(deleteBtn);

View File

@@ -0,0 +1,107 @@
// 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 { 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
*
* File gui/HiPSSelector.js
*
*
* Author: Thomas Boch, Matthieu Baumann[CDS]
*
*****************************************************************************/
// 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 Location.js
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import { Input } from "../Widgets/Input.js";
export class Dropdown extends Input {
// constructor
constructor(aladin, options) {
let self;
aladin.view.catalogCanvas.addEventListener('click', (e) => {
self.el.blur();
});
options.options = options.options || [];
super({
type: 'text',
autocomplete: {options: options.options},
...options
})
this.el.classList.add('search')
self = this;
this._addListeners(aladin);
}
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)
}
_addListeners(aladin) {
}
};

View File

@@ -1,172 +0,0 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { 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
*
* File gui/HiPSSelector.js
*
*
* Author: Thomas Boch, Matthieu Baumann[CDS]
*
*****************************************************************************/
// 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 Location.js
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import { Input } from "./../Widgets/Input.js";
import { Utils } from "../../Utils.ts";
export class HiPSSearch extends Input {
static HiPSList = {};
// constructor
constructor(aladin, options) {
let self;
let layer = options && options.layer;
aladin.view.catalogCanvas.addEventListener('click', (e) => {
self.el.blur();
});
let prevKey = layer.name;
let hips = HiPSSearch.HiPSList[layer.name];
let content = [new Icon({
size: 'medium',
monochrome: true,
url: infoIconUrl,
cssStyle: {
cursor: "help",
},
})];
let link;
if (layer.subtype === "fits") {
link = 'Download file...'
} else {
link = 'See more...'
}
content.push('<a style="color: white;" href="' + layer.url + '" target="_blank">' + link + '</a>')
let tooltip = {
content: new Layout({layout: content, orientation: 'horizontal'}),
hoverable: true,
position: {
direction: 'bottom',
}
}
super({
name: 'HiPS search',
type: 'text',
name: 'survey' + Utils.uuidv4(),
tooltip,
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, hips;
// A user can put an url
try {
image = new URL(key).href;
} catch(e) {
// Or he can select a HiPS from the list given
hips = HiPSSearch.HiPSList[key]
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 = image;
// set the layer to the new value
self.layer = aladin.setOverlayImageLayer(image, layer.layer);
}
}
},
value: layer.name,
...options
})
this.el.classList.add('aladin-HiPS-search', 'search')
self = this;
this.layer = layer;
this._addEventListeners(aladin);
}
setAutocompletionList(options) {
this.update({autocomplete: {options}})
}
_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})
}
});
}
};

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,13 +119,13 @@ export class Box extends DOMElement {
},
action(e) {}
});
dragElement(draggableEl.element(), this.el)
dragElement(titleEl, this.el)
titleEl.style.cursor = 'move'
}
Layout.horizontal([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");
@@ -130,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) {
@@ -151,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) {
@@ -166,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) {
@@ -176,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,22 +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);
//console.log(this.el, this.el.querySelector('#' + datalist.id), datalist)
/*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 {
@@ -164,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);
}
}
@@ -228,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

@@ -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],
}]
};