Compare commits

...

12 Commits

Author SHA1 Message Date
Matthieu Baumann
05d5dc131e fix: restore context after drawing overlay + rename Overlay -> GraphicOverlay 2024-05-21 17:35:10 +10:00
Philip Matsson
e764866033 Add documentation for the lineDash option 2024-05-21 14:37:47 +10:00
Philip Matsson
781668d6cb Add line dash option to Overlay class 2024-05-21 14:37:47 +10:00
Matthieu Baumann
fade1f95d2 only authorize giving shape's positions in icrs frame 2024-05-21 11:47:41 +10:00
Matthieu Baumann
ccb7347e54 css links in table color change 2024-05-20 11:30:42 +10:00
Matthieu Baumann
6ab5abae09 fix pixel res filtering 2024-05-19 22:10:46 +10:00
Matthieu Baumann
00f7005bce set url when querying simbad cone search as well 2024-05-19 17:43:14 +10:00
Matthieu Baumann
219761c512 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 11:51:35 +02:00
Matthieu Baumann
cd363ca4b9 multiple fixes for accessing SODA SKA services, HiPS browser, etc... 2024-05-16 11:51:35 +02:00
Matthieu Baumann
fda33df813 ui fixes and WIP browser 2024-05-16 11:51:35 +02:00
Matthieu Baumann
ee1c23e34f fix: select options remove multiple spaces 2024-05-16 11:51:35 +02:00
Matthieu Baumann
22db1baac7 first commit 2024-05-16 11:51:35 +02:00
61 changed files with 2875 additions and 1743 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

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

View File

@@ -31,7 +31,14 @@
selectionColor: 'white',
// Footprint associated to sources
shape: (s) => {
// discard drawing a vector for big pm
let totalPmSquared = s.data.pmra*s.data.pmra + s.data.pmdec*s.data.pmdec;
if (totalPmSquared > 6) {
return;
}
let color = rainbowColorMap((totalPmSquared - 2.5) / 2)
// Compute the mean of pm over the catalog sources
if (!pmraMean || !pmdecMean) {
pmraMean = 0, pmdecMean = 0;
@@ -46,27 +53,14 @@
pmdecMean /= numSources
}
console.log("mean", pmraMean, pmdecMean)
let dra = +s.data.pmra - pmraMean;
let ddec = +s.data.pmdec - pmdecMean;
// discard drawing a vector for big pm
let totalPmSquared = s.data.pmra*s.data.pmra + s.data.pmdec*s.data.pmdec;
if (totalPmSquared > 6) {
return;
}
let color = rainbowColorMap((totalPmSquared - 2.5) / 2)
return A.vector(
s.ra,
s.dec,
s.ra + dra,
s.dec + ddec,
null,
{lineWidth: 3, color}
)
}

View File

@@ -13,6 +13,10 @@
inertia: false,
fov: 1,
showContextMenu: true,
showZoomControl: true,
showSettingsControl: true,
showSimbadPointerControl: true,
samp: true,
});
// define custom draw function
@@ -24,12 +28,12 @@
color: "cyan",
hoverColor: "red",
shape: (s) => {
let galaxy = ["Seyfert","Seyfert_1", "Seyfert_2","LSB_G","PartofG","RadioG","Gin","GinPair","HII_G","LensedG","BClG","BlueCompG","EmG","GinCl","GinGroup","StarburstG","LINER","AGN","Galaxy"].some((n) => s.data.main_type.indexOf(n) >= 0);
if (!galaxy) return;
let a = +s.data.size_maj;
let b = +s.data.size_min;
let galaxy = ["Seyfert","Gin","StarburstG","LINER","AGN","Galaxy"].some((n) => s.data.main_type.indexOf(n) >= 0);
if (!galaxy) return;
let theta = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta, { color: "cyan" });
},

View File

@@ -21,7 +21,7 @@ var pessto = 'https://archive.eso.org/tap_cat/sync?REQUEST=doQuery&LANG=ADQL&MAX
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: 'https://alasky.cds.unistra.fr/DSS/DSSColor/', target: 'LMC', fov: 5, showContextMenu: true});
aladin = A.aladin('#aladin-lite-div', {survey: 'https://alasky.cds.unistra.fr/DSS/DSSColor/', target: 'LMC', fov: 5, showContextMenu: true, showSettingsControl:true, samp:true});
aladin.addCatalog(A.catalogFromURL('https://vizier.u-strasbg.fr/viz-bin/votable?-source=HIP2&-c=LMC&-out.add=_RAJ,_DEJ&-oc.form=dm&-out.meta=DhuL&-out.max=9999&-c.rm=180', {sourceSize:12, color: '#f08080'}));

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

@@ -12,7 +12,7 @@
A.init.then(() => {
// Start up Aladin Lite
aladin = A.aladin('#aladin-lite-div', {survey: "CDS/P/DSS2/color", target: 'M 1', fov: 0.2, showContextMenu: true, fullScreen: true});
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 3});
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 3, lineDash: [2, 2]});
aladin.addOverlay(overlay);
overlay.addFootprints([
A.polygon([[83.64287, 22.01713], [83.59872, 22.01692], [83.59852, 21.97629], [83.64295, 21.97629]], {hoverColor: 'green'}),

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

@@ -11,11 +11,11 @@
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {target: 'Gamma Cas', fov: 10});
aladin = A.aladin('#aladin-lite-div', {target: 'Gamma Cas', fov: 10, cooFrame: 'icrs'});
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 2});
var overlay = A.graphicOverlay({lineWidth: 2});
aladin.addOverlay(overlay);
overlay.add(A.polyline([ [2.29452158, 59.14978110], [10.12683778, 56.53733116], [14.1772154, 60.7167403], [21.45396446, 60.23528403], [28.59885697, 63.67010079] ]));
overlay.add(A.polyline([ [2.29452158, 59.14978110], [10.12683778, 56.53733116], [14.1772154, 60.7167403], [21.45396446, 60.23528403], [28.59885697, 63.67010079] ], {color: 'green'}));
});
</script>
</body>

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

@@ -1,88 +0,0 @@
<!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: "m51",
fov: 90,
projection: "AIT",
showContextMenu: true,
showShareControl: true,
hipsList: [
// High energy (Gamma and X-rays)
"CDS/P/HGPS/Flux",
"CDS/P/Fermi/5",
"CDS/P/Fermi/4",
"CDS/P/Fermi/3",
"ov-gso/P/Fermi/Band2",
"ov-gso/P/BAT/150-195keV",
"ov-gso/P/BAT/35-50keV",
"ov-gso/P/BAT/14-20keV",
"erosita/dr1/rate/023",
"erosita/dr1/rate/024",
// Uv/Optical/Infrared
"CDS/P/GALEXGR6_7/FUV",
"CDS/P/GALEXGR6_7/NUV",
"CDS/P/DSS2/color",
"CDS/P/PanSTARRS/DR1/g",
"CDS/P/PanSTARRS/DR1/r",
"CDS/P/Finkbeiner",
"CDS/P/PanSTARRS/DR1/i",
"CDS/P/PanSTARRS/DR1/color-i-r-g",
"CDS/P/PanSTARRS/DR1/z",
"CDS/P/PanSTARRS/DR1/y",
"CDS/P/DES-DR2/ColorIRG",
"CDS/P/2MASS/color",
"ov-gso/P/GLIMPSE/irac1",
"ov-gso/P/GLIMPSE/irac2",
"CDS/P/unWISE/color-W2-W1W2-W1",
"ov-gso/P/GLIMPSE/irac3",
"ov-gso/P/GLIMPSE/irac4",
"CDS/P/IRIS/color",
"ESAVO/P/AKARI/N60",
"ESAVO/P/AKARI/WideL",
"ESAVO/P/HERSCHEL/SPIRE-250",
"ESAVO/P/HERSCHEL/SPIRE-350",
"ESAVO/P/HERSCHEL/SPIRE-500",
// sub-mm/mm/radio
"CDS/P/PLANCK/R3/HFI/color",
"CDS/P/ACT_Planck/DR5/f220",
"CDS/P/CO",
"CDS/P/PLANCK/R3/HFI100",
"CDS/P/PLANCK/R3/LFI30",
"CDS/P/NVSS",
"CSIRO/P/RACS/mid/I",
"ov-gso/P/CGPS/VGPS",
"CDS/C/HI4PI/HI",
"CDS/P/MeerKAT/Galactic-Centre-1284MHz-StokesI",
"CSIRO/P/RACS/low/I",
"astron.nl/P/tgssadr",
"ov-gso/P/GLEAM/170-231",
"ov-gso/P/GLEAM/139-170",
"astron.nl/P/lotss_dr2_high",
"ov-gso/P/GLEAM/103-134",
"ov-gso/P/GLEAM/072-103",
],
});
aladin.setImageLayer(A.imageHiPS("astron.nl/P/lotss_dr2_high"));
aladin.addCatalog(
A.catalogFromSKAORucio("m51", 10, {
onClick: "showTable",
hoverColor: "yellow",
})
);
//aladin.addCatalog(A.catalogFromURL('https://aladin.cds.unistra.fr/ObsCoreRucioScs.xml', {onClick: 'showTable'}));
});
</script>
</body>
</html>

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.2-beta",
"description": "An astronomical HiPS visualizer in the browser",
"author": "Thomas Boch and Matthieu Baumann",
"license": "GPL-3",

View File

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

View File

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

View File

@@ -1433,6 +1433,10 @@ impl App {
crate::math::projection::screen_to_clip_space(pos, &self.camera)
}
pub(crate) fn get_coo_system(&self) -> CooSystem {
self.camera.get_coo_system()
}
pub(crate) fn view_to_icrs_coosys(&self, lonlat: &LonLatT<f64>) -> LonLatT<f64> {
let icrs_pos: Vector4<_> = lonlat.vector();
let view_system = self.camera.get_coo_system();

View File

@@ -2,18 +2,20 @@ use cgmath::{BaseFloat, Vector4};
use al_api::coo_system::CooBaseFloat;
use al_api::coo_system::CooSystem;
use crate::math::lonlat::LonLat;
/// This is conversion method returning a transformation
/// matrix when the system requested by the user is not
/// icrs j2000.
/// The core projections are always performed in icrs j2000
/// so one must call these methods to convert them to icrs before.
#[inline]
pub fn apply_coo_system<S>(c1: CooSystem, c2: CooSystem, v1: &Vector4<S>) -> Vector4<S>
pub fn apply_coo_system<S>(c1: CooSystem, c2: CooSystem, v: &Vector4<S>) -> Vector4<S>
where
S: BaseFloat + CooBaseFloat,
{
let c1_2_c2_mat = c1.to::<S>(c2);
c1_2_c2_mat * (*v1)
c1_2_c2_mat * (*v)
}
mod tests {

View File

@@ -625,8 +625,23 @@ impl WebClient {
///
/// * `lon` - A longitude in degrees
/// * `lat` - A latitude in degrees
#[wasm_bindgen(js_name = worldToScreen)]
pub fn world_to_screen(&self, lon: f64, lat: f64) -> Option<Box<[f64]>> {
#[wasm_bindgen(js_name = world2pix)]
pub fn world_to_pixel(
&self,
mut lon: f64,
mut lat: f64,
frame: Option<CooSystem>,
) -> Option<Box<[f64]>> {
if let Some(frame) = frame {
// first convert the coo to the view frame
use crate::math::lonlat::LonLat;
let xyz =
LonLatT::new(lon.to_radians().to_angle(), lat.to_radians().to_angle()).vector();
let lonlat = coosys::apply_coo_system(frame, CooSystem::ICRS, &xyz).lonlat();
lon = lonlat.lon().to_degrees();
lat = lonlat.lat().to_degrees();
}
self.app
.world_to_screen(lon, lat)
.map(|v| Box::new([v.x, v.y]) as Box<[f64]>)
@@ -700,11 +715,24 @@ impl WebClient {
///
/// * `pos_x` - The x screen coordinate in pixels
/// * `pos_y` - The y screen coordinate in pixels
#[wasm_bindgen(js_name = screenToWorld)]
pub fn screen_to_world(&self, pos_x: f64, pos_y: f64) -> Option<Box<[f64]>> {
/// * `frame` - If not given, the coo given will be in the current view frame
#[wasm_bindgen(js_name = pix2world)]
pub fn pixel_to_world(
&self,
pos_x: f64,
pos_y: f64,
frame: Option<CooSystem>,
) -> Option<Box<[f64]>> {
self.app
.screen_to_world(&Vector2::new(pos_x, pos_y))
.map(|lonlat| {
.map(|mut lonlat| {
if let Some(frame) = frame {
use crate::math::lonlat::LonLat;
let xyz = lonlat.vector();
lonlat =
coosys::apply_coo_system(self.app.get_coo_system(), frame, &xyz).lonlat();
}
let lon_deg: ArcDeg<f64> = lonlat.lon().into();
let lat_deg: ArcDeg<f64> = lonlat.lat().into();

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,31 @@
.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;
color: greenyellow;
}
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 +136,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 +326,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 +345,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 +396,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 +418,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 +467,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 +486,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 +501,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 +886,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 +905,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 +1097,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 +1133,10 @@ canvas {
height: 1.7rem;
}
.aladin-location .aladin-input-text {
width: 15rem;
}
.aladin-fov {
position: absolute;
bottom: 0.2rem;

View File

@@ -29,11 +29,12 @@
*****************************************************************************/
import { MOC } from "./MOC.js";
import { Overlay } from "./Overlay.js";
import { Circle } from "./Circle.js";
import { Ellipse } from "./Ellipse.js";
import { Polyline } from "./Polyline.js";
import { Line } from "./Line.js";
import { GraphicOverlay } from "./Overlay.js";
import { Circle } from "./shapes/Circle.js";
import { Ellipse } from "./shapes/Ellipse.js";
import { Polyline } from "./shapes/Polyline.js";
import { Line } from "./shapes/Line.js";
import { Catalog } from "./Catalog.js";
import { ProgressiveCat } from "./ProgressiveCat.js";
import { Source } from "./Source.js";
@@ -106,13 +107,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,
@@ -298,15 +299,14 @@ A.ellipse = function (ra, dec, radiusRaDeg, radiusDecDeg, rotationDeg, options)
* @param {number} dec1 - Declination (Dec) coordinate of the center in degrees.
* @param {number} ra2 - Right Ascension (RA) coordinate of the center in degrees.
* @param {number} dec2 - Declination (Dec) coordinate of the center in degrees.
* @param {CooFrame} [frame] - Frame in which the coordinates are given. If none, the frame used is icrs/j2000.
* @param {ShapeOptions} options - Options for configuring the vector.
*
* @returns {Line}
*/
A.vector = function (ra1, dec1, ra2, dec2, frame, options) {
A.vector = function (ra1, dec1, ra2, dec2, options) {
options['arrow'] = true;
return A.line(ra1, dec1, ra2, dec2, frame, options);
return A.line(ra1, dec1, ra2, dec2, options);
}
/**
@@ -319,13 +319,14 @@ A.vector = function (ra1, dec1, ra2, dec2, frame, options) {
* @param {Object} options - Options for configuring the graphic overlay.
* @param {string} [options.color] - The color of the graphic overlay.
* @param {number} [options.lineWidth] - The width of the lines in the graphic overlay.
* @param {Array} [options.lineDash] - The dash pattern for the lines in the graphic overlay.
* @returns {Overlay} Returns a new Overlay object representing the graphic overlay.
*
* @example
* var overlay = A.graphicOverlay({ color: '#ee2345', lineWidth: 3 });
* var overlay = A.graphicOverlay({ color: '#ee2345', lineWidth: 3, lineDash: [2, 4]});
*/
A.graphicOverlay = function (options) {
return new Overlay(options);
return new GraphicOverlay(options);
};
/**
@@ -376,7 +377,7 @@ A.coo = function (longitude, latitude, prec) {
* @returns {Array.<Polyline|Circle>} Returns a list of shapes from the STC-S string
*/
A.footprintsFromSTCS = function (stcs, options) {
var footprints = Overlay.parseSTCS(stcs, options);
var footprints = GraphicOverlay.parseSTCS(stcs, options);
return footprints;
}
@@ -684,14 +685,8 @@ A.catalogFromSimbad = function (target, radius, options, successCallback, errorC
const processVOTable = function (table) {
let {sources, fields} = table;
cat.setFields(fields);
if (cat.type === 'ObsCore') {
// The fields corresponds to obscore ones
// Set the name of the catalog to be ObsCore:<catalog name>
cat.name = "ObsCore:" + url;
}
cat.addSources(sources);
cat.url = url;
if (successCallback) {
successCallback(cat);
@@ -700,13 +695,8 @@ A.catalogFromSimbad = function (target, radius, options, successCallback, errorC
if (sources.length === 0) {
console.warn(cat.name + ' has no sources!')
}
// Even if the votable is not a proper ObsCore one, try to see if specific columns are given
// e.g. access_format and access_url
//ObsCore.handleActions(catalog);
};
Catalog.parseVOTable(
url,
processVOTable,

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

@@ -32,10 +32,34 @@
import { Utils } from './Utils';
import A from "./A.js";
import { Color } from './Color';
/**
* @typedef {Object} GraphicOverlayOptions
* @description Options for configuring the graphic overlay
*
* @property {Object} options - Configuration options for the MOC.
* @property {string} [options.name="overlay"] - The name of the catalog.
* @property {string} [options.color] - A string parsed as CSS <color> value. See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value| here}
* @property {number} [options.lineWidth=3] - The line width in pixels
* @property {Array.<number>} [options.lineDash=[]] - Dash line option. See the segments property {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#segments| here}
*/
export let Overlay = (function() {
let Overlay = function(options) {
/**
* Represents an overlay containing Footprints, whether it is
*
* @namespace
* @typedef {Object} GraphicOverlay
*/
export let GraphicOverlay = (function() {
/**
* Constructor function for creating a new graphical overlay instance.
*
* @constructor
* @memberof GraphicOverlay
* @param {GraphicOverlayOptions} options - Configuration options for the overlay.
*/
let GraphicOverlay = function(options) {
options = options || {};
this.uuid = Utils.uuidv4();
@@ -44,7 +68,8 @@ export let Overlay = (function() {
this.name = options.name || "overlay";
this.color = options.color || Color.getNextColor();
this.lineWidth = options["lineWidth"] || 2;
this.lineWidth = options["lineWidth"] || 3;
this.lineDash = options["lineDash"] || [];
//this.indexationNorder = 5; // at which level should we index overlays?
//this.overlays = [];
@@ -57,7 +82,7 @@ export let Overlay = (function() {
// TODO : show/hide methods should be integrated in a parent class
Overlay.prototype.show = function() {
GraphicOverlay.prototype.show = function() {
if (this.isShowing) {
return;
}
@@ -68,7 +93,7 @@ export let Overlay = (function() {
this.reportChange();
};
Overlay.prototype.hide = function() {
GraphicOverlay.prototype.hide = function() {
if (! this.isShowing) {
return;
}
@@ -79,7 +104,7 @@ export let Overlay = (function() {
this.reportChange();
};
Overlay.prototype.toggle = function() {
GraphicOverlay.prototype.toggle = function() {
if (! this.isShowing) {
this.show()
} else {
@@ -88,7 +113,7 @@ export let Overlay = (function() {
};
// return an array of Footprint from a STC-S string
Overlay.parseSTCS = function(stcs, options) {
GraphicOverlay.parseSTCS = function(stcs, options) {
options = options || {};
var footprints = [];
@@ -149,7 +174,7 @@ export let Overlay = (function() {
};
// ajout d'un tableau d'overlays (= objets Footprint, Circle ou Polyline)
Overlay.prototype.addFootprints = function(overlaysToAdd) {
GraphicOverlay.prototype.addFootprints = function(overlaysToAdd) {
for (var k=0, len=overlaysToAdd.length; k<len; k++) {
this.add(overlaysToAdd[k], false);
}
@@ -158,7 +183,7 @@ export let Overlay = (function() {
};
// TODO : item doit pouvoir prendre n'importe quoi en param (footprint, circle, polyline)
Overlay.prototype.add = function(item, requestRedraw) {
GraphicOverlay.prototype.add = function(item, requestRedraw) {
requestRedraw = requestRedraw !== undefined ? requestRedraw : true;
//if (item instanceof Footprint) {
@@ -176,7 +201,7 @@ export let Overlay = (function() {
// return a footprint by index
Overlay.prototype.getFootprint = function(idx) {
GraphicOverlay.prototype.getFootprint = function(idx) {
if (idx<this.footprints.length) {
return this.footprints[idx];
}
@@ -185,24 +210,26 @@ export let Overlay = (function() {
}
};
Overlay.prototype.setView = function(view) {
GraphicOverlay.prototype.setView = function(view) {
this.view = view;
};
Overlay.prototype.removeAll = function() {
GraphicOverlay.prototype.removeAll = function() {
// TODO : RAZ de l'index
//this.overlays = [];
this.overlayItems = [];
};
Overlay.prototype.draw = function(ctx) {
GraphicOverlay.prototype.draw = function(ctx) {
if (!this.isShowing) {
return;
}
ctx.save();
// simple drawing
ctx.strokeStyle= this.color;
ctx.lineWidth = this.lineWidth;
ctx.setLineDash(this.lineDash);
// 1. Drawing polygons
@@ -230,9 +257,11 @@ export let Overlay = (function() {
for (var k=0; k<this.overlayItems.length; k++) {
this.overlayItems[k].draw(ctx, this.view);
}
ctx.restore();
};
Overlay.increaseBrightness = function(hex, percent){
GraphicOverlay.increaseBrightness = function(hex, percent){
// strip the leading # if it's there
hex = hex.replace(/^\s*#|\s*$/g, '');
@@ -251,20 +280,25 @@ export let Overlay = (function() {
((0|(1<<8) + b + (256 - b) * percent / 100).toString(16)).substr(1);
};
Overlay.prototype.setColor = function(color) {
GraphicOverlay.prototype.setColor = function(color) {
this.color = color;
this.reportChange();
};
Overlay.prototype.setLineWidth = function(lineWidth) {
GraphicOverlay.prototype.setLineWidth = function(lineWidth) {
this.lineWidth = lineWidth;
this.reportChange();
};
GraphicOverlay.prototype.setLineDash = function(lineDash) {
this.lineDash = lineDash;
this.reportChange();
}
// callback function to be called when the status of one of the footprints has changed
Overlay.prototype.reportChange = function() {
GraphicOverlay.prototype.reportChange = function() {
this.view && this.view.requestRedraw();
};
return Overlay;
return GraphicOverlay;
})();

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

@@ -36,9 +36,9 @@ import { ProjectionEnum } from "./ProjectionEnum.js";
import { Utils } from "./Utils";
import { GenericPointer } from "./GenericPointer.js";
import { Stats } from "./libs/Stats.js";
import { Circle } from "./Circle.js";
import { Ellipse } from "./Ellipse.js";
import { Polyline } from "./Polyline.js";
import { Circle } from "./shapes/Circle.js";
import { Ellipse } from "./shapes/Ellipse.js";
import { Polyline } from "./shapes/Polyline.js";
import { CooFrameEnum } from "./CooFrameEnum.js";
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
import { WebGLCtx } from "./WebGL.js";
@@ -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,336 @@
// 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: "bottom",
aladin,
},
});
},
actionOff: (e) => {
self.filterBox._hide();
},
});
let filterNumberElt = document.createElement("div");
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, filterNumberElt])]),
]),
...options,
},
aladin.aladinDiv
);
this.filterBox = new HiPSFilterBox(aladin, {
callback: (params) => {
self._filterHiPSList(params);
},
})
this.filterNumberElt = filterNumberElt;
this.filterBox._hide();
this.searchDropdown = searchDropdown;
this.filterBtn = filterBtn;
this.aladin = aladin;
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
self = this;
this.filterCallback = (HiPS, params) => {
if (params.regime) {
if (!HiPS.obs_regime)
return false;
if (params.regime.toLowerCase() !== HiPS.obs_regime.toLowerCase()) {
return false;
}
}
if (params.spatial) {
if (!HiPS.ID)
return false;
if (Array.isArray(params.spatial) && !(params.spatial.includes(HiPS.ID))) {
return false;
}
}
if (params.resolution) {
if (!HiPS.hips_tile_width || !HiPS.hips_order) {
return false;
}
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) {
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 });
self.filterNumberElt.innerHTML = HiPSIDs.length + "/" + Object.keys(HiPSBrowserBox.HiPSList).length;
}
_hide() {
if (this.filterBox)
this.filterBox.signalBrowserStatus(true)
if (this.filterBtn && this.filterBtn.toggled) {
this.filterBtn.toggle();
}
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,233 @@
// 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;
if (this.on)
this._requestMOCServer();
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 = {
@@ -86,15 +86,14 @@ export class OverlayStackBox extends Box {
hoverColor: 'red',
onClick: "showTable",
shape: (s) => {
let galaxy = ["Seyfert","Seyfert_1", "Seyfert_2","LSB_G","PartofG","RadioG","Gin","GinPair","HII_G","LensedG","BClG","BlueCompG","EmG","GinCl","GinGroup","StarburstG","LINER","AGN","Galaxy"].some((n) => s.data.main_type.indexOf(n) >= 0);
if (!galaxy) return;
let a = +s.data.size_maj;
let b = +s.data.size_min;
let galaxy = ['Seyfert', 'Gin', 'StarburstG', 'LINER', 'AGN', 'Galaxy'].some((n) => s.data.main_type.indexOf(n) >= 0)
if (!galaxy)
return;
let theta = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta);
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta, { color: "cyan" });
}
},
},
@@ -138,24 +137,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 +507,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 +525,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 +603,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 +701,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 +718,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 +732,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 +766,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 +875,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 +994,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 +1014,7 @@ export class OverlayStackBox extends Box {
});
}
btn.update({
loadMOCBtn.update({
toggled: true,
tooltip: {
content: "Remove coverage",
@@ -1149,7 +1040,7 @@ export class OverlayStackBox extends Box {
});
}
btn.update({
loadMOCBtn.update({
toggled: false,
tooltip: {
content: "Add coverage",
@@ -1162,7 +1053,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

@@ -139,6 +139,7 @@ options = {
for (const objects of aladin.view.selection) {
let s0 = getSource(objects[0]);
const cat = s0.catalog;
console.log(cat)
const {url, name} = cat;
conn.loadVOTable(url, name, url);

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

@@ -28,8 +28,8 @@
*
*****************************************************************************/
import { Utils } from "./Utils";
import { Overlay } from "./Overlay.js";
import { Utils } from "./../Utils";
import { GraphicOverlay } from "./../Overlay.js";
/**
* Represents an circle shape
@@ -43,13 +43,13 @@ export let Circle = (function() {
*
* @constructor
* @memberof Circle
* @param {number[]} center - right-ascension/declination 2-tuple of the circle's center in degrees
* @param {number[]} centerRaDec - right-ascension/declination 2-tuple of the circle's center in degrees
* @param {number} radius - radius in degrees
* @param {ShapeOptions} options - Configuration options for the circle
*
* @returns {Circle} - The circle shape object
*/
let Circle = function(center, radius, options) {
let Circle = function(centerRaDec, radius, options) {
options = options || {};
this.color = options['color'] || undefined;
@@ -61,13 +61,14 @@ export let Circle = (function() {
// TODO : all graphic overlays should have an id
this.id = 'circle-' + Utils.uuidv4();
this.setCenter(center);
this.setCenter(centerRaDec);
this.setRadius(radius);
this.overlay = null;
this.isShowing = true;
this.isSelected = false;
this.isHovered = false;
this.frame = options.frame || "icrs";
};
Circle.prototype.setColor = function(color) {
@@ -253,10 +254,10 @@ export let Circle = (function() {
if(this.selectionColor) {
ctx.strokeStyle = this.selectionColor;
} else {
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
ctx.strokeStyle = GraphicOverlay.increaseBrightness(baseColor, 50);
}
} else if (this.isHovered) {
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
ctx.strokeStyle = this.hoverColor || GraphicOverlay.increaseBrightness(baseColor, 25);
} else {
ctx.strokeStyle = baseColor;
}

View File

@@ -28,9 +28,8 @@
*
*****************************************************************************/
import { Utils } from "./Utils";
import { Overlay } from "./Overlay.js";
import { requestAnimFrame } from "./libs/RequestAnimationFrame";
import { Utils } from "./../Utils";
import { GraphicOverlay } from "./../Overlay.js";
/**
* @typedef {Object} ShapeOptions
@@ -58,7 +57,7 @@ export let Ellipse = (function() {
*
* @constructor
* @memberof Ellipse
* @param {number[]} center - right-ascension/declination 2-tuple of the ellipse's center in degrees
* @param {number[]} centerRaDec - right-ascension/declination 2-tuple of the ellipse's center in degrees
* @param {number} a - semi-major axis length in degrees
* @param {number} b - semi-minor axis length in degrees
* @param {number} theta - angle of the ellipse in degrees
@@ -66,7 +65,7 @@ export let Ellipse = (function() {
*
* @returns {Ellipse} - The ellipse shape object
*/
let Ellipse = function(center, a, b, theta, options) {
let Ellipse = function(centerRaDec, a, b, theta, options) {
options = options || {};
this.color = options['color'] || undefined;
@@ -79,7 +78,7 @@ export let Ellipse = (function() {
// TODO : all graphic overlays should have an id
this.id = 'ellipse-' + Utils.uuidv4();
this.setCenter(center);
this.setCenter(centerRaDec);
this.setAxisLength(a, b);
this.setRotation(theta);
this.overlay = null;
@@ -197,7 +196,7 @@ export let Ellipse = (function() {
this.overlay.reportChange();
}
}
Ellipse.prototype.setCenter = function(centerRaDec) {
this.centerRaDec = centerRaDec;
if (this.overlay) {
@@ -279,34 +278,7 @@ export let Ellipse = (function() {
// 5. Get the correct ellipse angle
let theta = -this.rotation + westToNorthAngle;
//let ct = Math.cos(theta);
//let st = Math.sin(theta);
/*let circlePtXyViewRa = view.aladin.world2pix(view.viewCenter.lon + 1.0, view.viewCenter.lat);
let circlePtXyViewDec = view.aladin.world2pix(view.viewCenter.lon, view.viewCenter.lat + 1.0);
if (!circlePtXyViewRa || !circlePtXyViewDec) {
// the circle border goes out of the projection
// we do not draw it
return;
}
var dxRa = circlePtXyViewRa[0] - centerXyview[0];
var dyRa = circlePtXyViewRa[1] - centerXyview[1];
var dRa = Math.sqrt(dxRa*dxRa + dyRa*dyRa);
var dxDec = circlePtXyViewDec[0] - centerXyview[0];
var dyDec = circlePtXyViewDec[1] - centerXyview[1];
var dDec = Math.sqrt(dxDec*dxDec + dyDec*dyDec);*/
//var radiusInPixX = Math.abs(this.a * ct * dRa) + Math.abs(this.a * st * dDec);
//var radiusInPixY = Math.abs(this.b * st * dRa) + Math.abs(this.b * ct * dDec);
// Ellipse crossing the projection
/*if ((dxRa*dyDec - dxDec*dyRa) <= 0.0) {
// We do not draw it
return;
}*/
noStroke = noStroke===true || false;
// TODO : check each 4 point until show
@@ -322,10 +294,10 @@ export let Ellipse = (function() {
if(this.selectionColor) {
ctx.strokeStyle = this.selectionColor;
} else {
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
ctx.strokeStyle = GraphicOverlay.increaseBrightness(baseColor, 50);
}
} else if (this.isHovered) {
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
ctx.strokeStyle = this.hoverColor || GraphicOverlay.increaseBrightness(baseColor, 25);
} else {
ctx.strokeStyle = baseColor;
}

View File

@@ -30,8 +30,8 @@
*
*****************************************************************************/
import { Polyline } from "./Polyline.js";
import { Utils } from './Utils';
import { Overlay } from "./Overlay.js";
import { Utils } from '../Utils';
import { GraphicOverlay } from "../Overlay.js";
import { Ellipse } from "./Ellipse.js";
/**
@@ -50,13 +50,12 @@ export let Line = (function() {
* @param {number} dec1 - Declination (Dec) coordinate of the center in degrees.
* @param {number} ra2 - Right Ascension (RA) coordinate of the center in degrees.
* @param {number} dec2 - Declination (Dec) coordinate of the center in degrees.
* @param {CooFrame} [frame] - Frame in which the coordinates are given. If none, the frame used is icrs/j2000.
* @param {ShapeOptions} options - Options for configuring the line. Additional properties:
* @param {boolean} [options.arrow=false] - Add an arrow pointing from (ra1, dec1) to (ra2, dec2)
*
* @returns {Line} - The line shape object
*/
let Line = function(ra1, dec1, ra2, dec2, frame, options) {
let Line = function(ra1, dec1, ra2, dec2, options) {
options = options || {};
this.color = options['color'] || undefined;
this.opacity = options['opacity'] || undefined;
@@ -78,7 +77,6 @@ export let Line = (function() {
this.dec1 = dec1;
this.ra2 = ra2;
this.dec2 = dec2;
this.frame = frame;
};
Line.prototype = {
@@ -104,10 +102,10 @@ export let Line = (function() {
noStroke = noStroke===true || false;
noSmallCheck = noSmallCheck===true || false;
// project
const v1 = view.aladin.world2pix(this.ra1, this.dec1, this.frame);
const v1 = view.aladin.world2pix(this.ra1, this.dec1);
if (!v1)
return false;
const v2 = view.aladin.world2pix(this.ra2, this.dec2, this.frame);
const v2 = view.aladin.world2pix(this.ra2, this.dec2);
if (!v2)
return false;
@@ -133,9 +131,9 @@ export let Line = (function() {
}
if (this.isSelected) {
ctx.strokeStyle = this.selectionColor || Overlay.increaseBrightness(baseColor, 50);
ctx.strokeStyle = this.selectionColor || GraphicOverlay.increaseBrightness(baseColor, 50);
} else if (this.isHovered) {
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
ctx.strokeStyle = this.hoverColor || GraphicOverlay.increaseBrightness(baseColor, 25);
} else {
ctx.strokeStyle = baseColor;
}

View File

@@ -33,10 +33,9 @@
*
*****************************************************************************/
import { Utils } from './Utils';
import { Overlay } from "./Overlay.js";
import { ProjectionEnum } from "./ProjectionEnum.js";
import { Utils } from '../Utils';
import { GraphicOverlay } from "../Overlay.js";
import { ProjectionEnum } from "../ProjectionEnum.js";
/**
* Represents a polyline shape
@@ -97,13 +96,13 @@ export let Polyline = (function() {
*
* @constructor
* @memberof Polyline
* @param {Array.<number[]>} radecArray - right-ascension/declination 2-tuple array describing the polyline's vertices in degrees
* @param {Array.<number[]>} raDecArray - right-ascension/declination 2-tuple array describing the polyline's vertices in degrees
* @param {ShapeOptions} options - Configuration options for the polyline. Additional properties:
* @param {boolean} [options.closed=false] - Close the polyline, default to false.
*
* @returns {Polyline} - The polyline shape object
*/
let Polyline = function(radecArray, options) {
let Polyline = function(raDecArray, options) {
options = options || {};
this.color = options['color'] || undefined;
this.fill = options['fill'] || false;
@@ -118,7 +117,7 @@ export let Polyline = (function() {
// All graphics overlay have an id
this.id = 'polyline-' + Utils.uuidv4();
this.radecArray = radecArray;
this.raDecArray = raDecArray;
this.overlay = null;
this.isShowing = true;
@@ -246,7 +245,7 @@ export let Polyline = (function() {
return false;
}
if (! this.radecArray || this.radecArray.length<2) {
if (! this.raDecArray || this.raDecArray.length<2) {
return false;
}
@@ -268,10 +267,10 @@ export let Polyline = (function() {
if(this.selectionColor) {
ctx.strokeStyle = this.selectionColor;
} else {
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
ctx.strokeStyle = GraphicOverlay.increaseBrightness(baseColor, 50);
}
} else if (this.isHovered) {
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
ctx.strokeStyle = this.hoverColor || GraphicOverlay.increaseBrightness(baseColor, 25);
} else {
ctx.strokeStyle = baseColor;
}
@@ -279,7 +278,7 @@ export let Polyline = (function() {
// 1. project the vertices into the screen
// and computes a BBox
let xyView = [];
let len = this.radecArray.length;
let len = this.raDecArray.length;
let xmin = Number.POSITIVE_INFINITY
let xmax = Number.NEGATIVE_INFINITY
@@ -287,7 +286,7 @@ export let Polyline = (function() {
let ymax = Number.NEGATIVE_INFINITY;
for (var k=0; k<len; k++) {
var xyview = view.aladin.world2pix(this.radecArray[k][0], this.radecArray[k][1]);
var xyview = view.aladin.world2pix(this.raDecArray[k][0], this.raDecArray[k][1]);
if (!xyview) {
return false;
}
@@ -459,8 +458,8 @@ export let Polyline = (function() {
ctx.lineWidth = this.lineWidth;
let pointXY = [];
for (var j = 0; j < this.radecArray.length; j++) {
var xy = view.aladin.world2pix(this.radecArray[j][0], this.radecArray[j][1]);
for (var j = 0; j < this.raDecArray.length; j++) {
var xy = view.aladin.world2pix(this.raDecArray[j][0], this.raDecArray[j][1]);
if (!xy) {
return false;
}

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,92 @@ 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'}},
})
cubeDisplayer.update({content: Layout.horizontal([prevBtn, nextBtn, slicer, (idxSlice + 1) + '/' + numSlices])})
};
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, (idxSlice + 1) + '/' + numSlices]),
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 +261,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],
}]
};

View File

@@ -192,7 +192,7 @@ export class SAMPConnector {
this._pushMsgToAllClients(mtype, params);
if (!this.connected) {
let warnMsg = 'Please connect the client. Go to menu Settings -> SAMP';
let warnMsg = 'Please connect the client. Go to Settings (gear icon) -> SAMP';
alert(warnMsg);
throw warnMsg;
}