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/

This commit is contained in:
Matthieu Baumann
2024-05-16 10:27:27 +02:00
committed by Matthieu Baumann
parent cd363ca4b9
commit 219761c512
28 changed files with 592 additions and 379 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

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

@@ -12,7 +12,7 @@
aladin = A.aladin("#aladin-lite-div", {
fullScreen: true,
target: "m51",
fov: 90,
fov: 15,
projection: "AIT",
showContextMenu: true,
showShareControl: true,
@@ -76,8 +76,8 @@ aladin = A.aladin("#aladin-lite-div", {
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

@@ -18,13 +18,12 @@
console.log("zoomChanged")
})
aladin.on("positionChanged", ({ra, dec}) => {
console.log('call to aladin', aladin.pix2world(0, 0))
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

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

@@ -96,6 +96,7 @@
.aladin-measurement-div.aladin-dark-theme table thead {
background-color: #000;
color: white;
}
.aladin-measurement-div table th {
@@ -107,7 +108,7 @@
}
.aladin-measurement-div table tr td {
padding: 0.5rem 0;
padding: 0.5rem;
}
.aladin-measurement-div table tr td, .aladin-measurement-div table tr td a {
@@ -343,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 {
@@ -387,7 +395,7 @@ canvas {
height: 1.7rem;
}
.aladin-input-text.aladin-dark-theme:focus {
.aladin-input-text.aladin-dark-theme:focus, .aladin-input-number.aladin-dark-theme:focus {
border-color: dodgerblue;
}
@@ -458,7 +466,7 @@ canvas {
align-items: flex-end;
}
.aladin-vertical-list > *:first-of-type {
.aladin-vertical-list > *:first-child {
margin-top: 0;
}
@@ -477,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;
@@ -489,6 +500,10 @@ 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%;
@@ -870,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 */
@@ -884,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;
@@ -1090,8 +1106,12 @@ canvas {
width: 17rem;
}
.aladin-HiPS-filter-box {
border: 1px solid white;
}
.aladin-HiPS-browser-box .aladin-input-text {
width: 30vw;
width: 300px;
padding: 0.5rem;
}

View File

@@ -106,7 +106,7 @@ A.aladin = function (divSelector, options) {
* @function
* @name A.imageHiPS
* @memberof A
* @param {string} id - 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

View File

@@ -1574,6 +1574,20 @@ export let Aladin = (function () {
*/
Aladin.createImageSurvey = Aladin.prototype.createImageSurvey;
/**
* Creates a FITS image object
* @deprecated prefer use {@link A.imageFITS}
*
* @function createImageFITS
* @memberof Aladin
* @static
* @param {string} url - The url of the fits.
* @param {string} [name] - A human readable name for that fits.
* @param {ImageFITSOptions} [options] - Options for rendering the image
* @param {function} [success] - A success callback
* @param {function} [error] - A success callback
* @returns {ImageFITS} A FITS image object.
*/
Aladin.prototype.createImageFITS = function (
url,
name,
@@ -1592,7 +1606,7 @@ export let Aladin = (function () {
url = url.toString();
// Do not use proxy with CORS headers until we solve that: https://github.com/MattiasBuelens/wasm-streams/issues/20
//url = Utils.handleCORSNotSameOrigin(url);
//url = Utils.handleCORSNotSameOrigin(url).href;
let image = HiPSCache.get(url);
if (!image) {
@@ -1605,6 +1619,7 @@ export let Aladin = (function () {
};
/**
* @deprecated prefer use {@link A.imageFITS} instead
* Creates a FITS image object
*
* @function createImageFITS

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;

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;
};
@@ -182,12 +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
@@ -472,6 +463,7 @@ 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,

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)
}
}
}
},
@@ -1930,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

@@ -29,6 +29,8 @@ 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
@@ -83,7 +85,7 @@ export class HiPSBrowserBox extends Box {
let searchDropdown = new Dropdown(aladin, {
name: "HiPS browser",
placeholder: "Browser a HiPS by an URL, ID or keywords",
placeholder: "Browse a HiPS by an URL, ID or keywords",
tooltip: {
global: true,
aladin,
@@ -103,6 +105,10 @@ export class HiPSBrowserBox extends Box {
}
},
input(e) {
self.infoCurrentHiPSBtn.update({
disable: true,
})
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
@@ -117,6 +123,7 @@ export class HiPSBrowserBox extends Box {
checked: false,
tooltip: {
content: "Filter off",
position: {direction: 'left'},
},
click(e) {
let on = e.target.checked;
@@ -139,14 +146,29 @@ export class HiPSBrowserBox extends Box {
filterEnabler.update({
tooltip: {
content: on
? "Filtering on"
: "Filtering off",
? "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,
@@ -185,8 +207,8 @@ export class HiPSBrowserBox extends Box {
},
classList: ['aladin-HiPS-browser-box'],
content: Layout.vertical([
Layout.horizontal(["Filter:", filterEnabler, filterBtn]),
Layout.horizontal(["Search:", searchDropdown]),
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn])]),
]),
...options,
},
@@ -204,6 +226,8 @@ export class HiPSBrowserBox extends Box {
this.filterBtn = filterBtn;
this.aladin = aladin;
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
self = this;
this.filterCallback = (HiPS, params) => {
@@ -241,6 +265,14 @@ export class HiPSBrowserBox extends Box {
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');
@@ -252,6 +284,7 @@ export class HiPSBrowserBox extends Box {
// This method is executed only if the filter is enabled
_filterHiPSList(params) {
console.log("update dropdown")
let self = this;
let HiPSIDs = [];

View File

@@ -25,6 +25,8 @@ 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
*
@@ -41,7 +43,7 @@ export class HiPSFilterBox extends Box {
let regimeBtn = new TogglerActionButton({
content: 'Regime',
tooltip: {content: 'Observation regime'},
tooltip: {content: 'Observation regime', position: {direction: 'bottom'}},
toggled: true,
actionOn: () => {
self._triggerFilteringCallback();
@@ -51,19 +53,19 @@ export class HiPSFilterBox extends Box {
}
});
let spatialBtn = new TogglerActionButton({
content: 'Spatial',
tooltip: {content: 'Check for HiPS having observation in the view!'},
content: 'Inside view',
tooltip: {content: 'Check for HiPS having observation in the view!', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
self._requestMOCServer();
},
actionOff: () => {
self._triggerFilteringCallback();
}
});
let resolutionBtn = new TogglerActionButton({
content: 'Resolution',
tooltip: {content: 'Check for HiPS with a specific pixel resolution.'},
content: 'Pixel res',
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
@@ -73,13 +75,37 @@ export class HiPSFilterBox extends Box {
}
});
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([
'<h3>Filters:</h3>',
'<b>Filter by:</b>',
Layout.horizontal([regimeBtn, spatialBtn, resolutionBtn]),
'<h3>Parameters:</h3>',
'<b>Details:</b>',
new Form({
subInputs: [
{
@@ -91,10 +117,12 @@ export class HiPSFilterBox extends Box {
value: "Optical",
type: 'select',
options: [
"Optical",
"UV",
"Radio",
"Infrared",
"Millimeter",
"Optical",
"UV",
"EUV",
"X-ray",
"Gamma-ray",
],
@@ -111,39 +139,7 @@ export class HiPSFilterBox extends Box {
position: { direction: "right" },
},
},
{
label: "Angular res/px:",
name: "res",
value: "1°",
type: 'text',
classList: ['aladin-valid'],
autocomplete: 'off',
actions: {
input: (e) => {
e.target.classList.remove('aladin-not-valid');
e.target.classList.remove('aladin-valid');
let value = e.target.value;
let resolution = new Angle();
if (resolution.parse(value)) {
// The angle has been parsed
console.log(resolution.degrees())
self.params["resolution"] = resolution.degrees();
e.target.classList.add('aladin-valid');
//resolutionBtn.update({content: '<=' + value});
self._triggerFilteringCallback();
} else {
e.target.classList.add('aladin-not-valid');
}
},
change: (e) => {
e.preventDefault();
e.stopPropagation();
}
}
},
logSlider
],
},
],

View File

@@ -74,8 +74,6 @@ export class ServiceQueryBox extends Box {
// Tackle CORS problems
Utils.loadFromUrls([url, Utils.handleCORSNotSameOrigin(url)], {timeout: 30000, dataType: 'blob'})
.then((blob) => {
console.log("azer", blob)
const url = URL.createObjectURL(blob);
try {
@@ -86,7 +84,6 @@ export class ServiceQueryBox extends Box {
}
})
.catch((e) => {
console.log("jjaaz")
window.alert(e)
})
},
@@ -153,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

@@ -52,6 +52,7 @@ 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 = {
@@ -1053,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

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

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

View File

@@ -59,6 +59,7 @@ export class Form extends DOMElement {
el.className = "aladin-form";
super(el, options);
this.attachTo(target, position)
this._show()
@@ -70,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))
});
}
@@ -94,17 +95,23 @@ export class Form extends DOMElement {
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]);
@@ -118,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

@@ -7,12 +7,16 @@ import { Format } from "./coo";
* Creates an angle of the Aladin interactive sky atlas.
* @class
* @constructs Angle
* @param {number} angle - precision in degrees
* @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;
};

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

@@ -238,21 +238,20 @@
return accessUrlEl;
},
/*'access_format': (data) => {
'access_format': (data) => {
let accessFormat = data['access_format'];
if (accessFormat && accessFormat.includes('datalink')) {
return new ActionButton({
size: 'small',
content: '🔗',
tooltip: {content: accessFormat, position: {direction: 'right'}},
tooltip: {content: 'Datalink VOTable', aladin: aladinInstance, global: true},
action(e) {}
}).element();
return accessFormat;
} else {
return accessFormat;
}
}*/
}
}
};

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