Compare commits

...

9 Commits

Author SHA1 Message Date
Matthieu Baumann
5c08833f8a new UI StackBox 2024-04-23 00:17:07 +02:00
Matthieu Baumann
7f83ccc04e save the imageHiPS once it has been changed so that the HiPS stack is saved. One can go back from the UI afterwards 2024-04-12 19:23:47 +02:00
Matthieu Baumann
56cb5c0dba add zoom heuristic for mouse 2024-04-12 19:12:55 +02:00
Matthieu Baumann
53d44de229 update changelog 2024-04-12 14:33:01 +02:00
Matthieu Baumann
3dcdeeec0c For Catalog only: plot the sources when the footprints are too small 2024-04-12 11:50:25 +02:00
Matthieu Baumann
a1434f781b add doc for the different shapes 2024-04-05 11:52:23 +02:00
Matthieu Baumann
aa09263c04 add proper motion example 2024-04-04 17:41:11 +02:00
Matthieu Baumann
9af2d835ff expose Line in the API and add an example with proper motions drawn from a Simbad CS around the LMC 2024-04-03 16:06:13 +02:00
Matthieu Baumann
dc51517758 first commit 2024-04-02 18:32:04 +02:00
72 changed files with 3181 additions and 987 deletions

View File

@@ -1,5 +1,13 @@
# Changelogs
## 3.3.3
* [feat] New `hipsList` option parameter when instancing a new Aladin object.
* [feat] Zoom smoothing using hermite cubic interpolation functions
* [feat] shape option of Catalog and ProgressiveCat accepts a function returning a Footprint. This allow user to
associate a footprint to a specific source
* [feat] Hover color support by @pmatsson and @bmatthieu3 in <https://github.com/cds-astro/aladin-lite/pull/145>
## 3.3.2
* [fixed] do not allow to query the properties several times for an imageHiPS

5
assets/icons/add.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44771 13 8V11H16C16.5523 11 17 11.4477 17 12C17 12.5523 16.5523 13 16 13H13V16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16V13H8C7.44772 13 7 12.5523 7 12C7 11.4477 7.44771 11 8 11H11V8Z" fill="#0F0F0F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23 4C23 2.34315 21.6569 1 20 1H4C2.34315 1 1 2.34315 1 4V20C1 21.6569 2.34315 23 4 23H20C21.6569 23 23 21.6569 23 20V4ZM21 4C21 3.44772 20.5523 3 20 3H4C3.44772 3 3 3.44772 3 4V20C3 20.5523 3.44772 21 4 21H20C20.5523 21 21 20.5523 21 20V4Z" fill="#0F0F0F"/>
</svg>

After

Width:  |  Height:  |  Size: 811 B

View File

@@ -10,7 +10,7 @@
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: ["P/PanSTARRS/DR1/color-i-r-g"], showReticle: false, gridOptions: {opacity: 0.5, color: 'rgba(255, 0, 0)'}, projection: "AIT", cooFrame: 'icrs', target: "stephan's quintet", fov: 1000, showGotoControl: false, showFrame: false, fullScreen: true, showLayersControl: true, showCooGrid: true, showCooGridControl: false});
aladin = A.aladin('#aladin-lite-div', {survey: ["P/PanSTARRS/DR1/color-i-r-g"], showReticle: false, projection: "AIT", cooFrame: 'icrs', target: "stephan's quintet", fov: 1000, showGotoControl: false, showFrame: false, fullScreen: true, showLayersControl: true, showCooGrid: true, showCooGridControl: false});
const chft = aladin.createImageSurvey('CFHT', "CFHT deep view of NGC7331 and Stephan's quintet u+g+r", "https://cds.unistra.fr/~derriere/PR_HiPS/2022_Duc/", null, null, {imgFormat: 'png'});
const nircamJWST = aladin.createImageSurvey('Nircam', "Stephans Quintet NIRCam+MIRI", "http://alasky.cds.unistra.fr/JWST/CDS_P_JWST_Stephans-Quintet_NIRCam+MIRI/", null, null, {imgFormat: 'png', colormap: "viridis"});

View File

@@ -15,10 +15,8 @@
</div>
</div>
</div>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', cooFrame: 'galactic', fov: 360, fullScreen: true, showCooGrid: false, showReticle: false})
aladin.gotoRaDec(79.9525321, -69.2742586)
@@ -52,8 +50,8 @@
}
}
aladin.view.setGridConfig({opacity: 0, color: {r: 51/255, g: 209/255, b: 1}})
aladin.view.setGridConfig({enabled: true})
aladin.setCooGrid({opacity: 0, color: {r: 51/255, g: 209/255, b: 1}})
aladin.setCooGrid({enabled: true})
async function s_1() {
return new Promise((resolve, reject) => {
@@ -98,7 +96,7 @@
async function showGrid() {
for await(const it of interval(50, 40)) {
aladin.view.setGridConfig({opacity: it / 40})
aladin.setCooGrid({opacity: it / 40})
}
}
@@ -155,7 +153,7 @@
async function hideGrid() {
for await(const it of interval(50, 40)) {
aladin.view.setGridConfig({opacity: 1 - it / 40})
aladin.setCooGrid({opacity: 1 - it / 40})
}
}

View File

@@ -4,7 +4,7 @@
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<div id="aladin-lite-div" style="width: 1024px; height: 100%;"></div>
<script type="module">
import A from '../src/js/A.js';
@@ -19,7 +19,6 @@
target: '19 24 51.556 +45 16 44.36', // initial target
cooFrame: 'equatorial', // set galactic frame
showCooGrid: true, // set the grid
fullScreen: true,
}
);
});

View File

@@ -0,0 +1,45 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script>let aladin;</script>
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
// Start up Aladin Lite
aladin = A.aladin('#aladin-lite-div', {
target: "M31",
fov: 89.78,
showContextMenu: true,
fullScreen: true,
showSimbadPointerControl: true,
showShareControl: true,
showSettingsControl: true,
showStackLayerControl: true,
samp: true,
});
aladin.addCatalog(A.catalogFromVizieR("VII/237/pgc", "M31", 3, {
limit: 1000,
//orderBy: 'nb_ref',
onClick: 'showTable',
color: 'yellow',
hoverColor: 'blue',
shape: (s) => {
let coo = A.coo();
coo.parse(s.data['RAJ2000'] + ' ' + s.data['DEJ2000'])
let a = (0.1 * Math.pow(10, +s.data.logD25)) / 60;
let b = (1.0 / Math.pow(10, +s.data.logR25)) * a
return A.ellipse(coo.lon, coo.lat, a, b, +s.data.PA, {lineWidth: 3});
}
}));
});
</script>
</body>
</html>

View File

@@ -0,0 +1,117 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script>let aladin;</script>
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
// Start up Aladin Lite
aladin = A.aladin('#aladin-lite-div', {
target: "LMC",
fov: 10,
showContextMenu: true,
fullScreen: true,
showSimbadPointerControl: true,
showShareControl: true,
showSettingsControl: true,
showStackLayerControl: true,
samp: true,
});
let pmraMean = null, pmdecMean = null;
//aladin.addCatalog(A.catalogFromSimbad('LMC', 2.5, {
//aladin.addCatalog(A.catalogFromVizieR('J/A+A/663/A107/table5', 'LMC', 5, {
const pmCat = A.catalogFromURL('./data/proper_motion.xml', {
onClick: 'showTable',
name: 'mean pm over HPX cells around LMC from GaiaDR2',
hoverColor: 'yellow',
selectionColor: 'white',
// Footprint associated to sources
shape: (s) => {
// Compute the mean of pm over the catalog sources
if (!pmraMean || !pmdecMean) {
pmraMean = 0, pmdecMean = 0;
for (var s of pmCat.getSources()) {
pmraMean += +s.data.pmra;
pmdecMean += +s.data.pmdec;
}
const numSources = pmCat.getSources().length;
pmraMean /= numSources
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}
)
}
});
aladin.addCatalog(pmCat);
});
function rainbowColorMap(value) {
// Ensure value is within range [0, 1]
value = Math.max(0, Math.min(1, value));
// Convert value to hue
var hue = (1 - value) * 240; // 240 is the maximum hue value for blue
// Convert HSV to RGB
var chroma = 1;
var x = chroma * (1 - Math.abs((hue / 60) % 2 - 1));
var r1, g1, b1;
if (hue >= 0 && hue < 60) {
[r1, g1, b1] = [chroma, x, 0];
} else if (hue >= 60 && hue < 120) {
[r1, g1, b1] = [x, chroma, 0];
} else if (hue >= 120 && hue < 180) {
[r1, g1, b1] = [0, chroma, x];
} else if (hue >= 180 && hue < 240) {
[r1, g1, b1] = [0, x, chroma];
}
var m = 1 - chroma;
var r = r1 + m;
var g = g1 + m;
var b = b1 + m;
// Convert RGB to HEX
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
var colorHex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
return colorHex;
}
</script>
</body>
</html>

View File

@@ -10,28 +10,22 @@
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {target: 'LMC', fov: 55, showContextMenu: true});
aladin = A.aladin('#aladin-lite-div', {target: '12 25 41.512 +12 48 47.2', inertia: false, fov: 1, showContextMenu: true});
// define custom draw function
var drawFunction = function(source, canvasCtx, viewParams) {
canvasCtx.beginPath();
canvasCtx.arc(source.x, source.y, source.data['coo_err_min'] * 5, 0, 2 * Math.PI, false);
canvasCtx.closePath();
canvasCtx.strokeStyle = '#c38';
canvasCtx.lineWidth = 3;
canvasCtx.globalAlpha = 0.7,
canvasCtx.stroke();
var fov = Math.max(viewParams['fov'][0], viewParams['fov'][1]);
// object name is displayed only if fov<10°
if (fov>10) {
var drawFunctionFootprint = function(s) {
let a = +s.data.size_maj;
let b = +s.data.size_min;
let galaxy = ['Seyfert', 'Gin', 'StarburstG', 'LINER', 'AGN', 'Galaxy'].some((n) => s.data.main_type.indexOf(n) >= 0)
if (!galaxy)
return;
}
canvasCtx.globalAlpha = 0.9;
canvasCtx.globalAlpha = 1;
let theta = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta, {color: 'cyan'});
};
var hips = A.catalogHiPS('https://axel.u-strasbg.fr/HiPSCatService/Simbad', {onClick: 'showTable', name: 'Simbad', shape: drawFunction});
var hips = A.catalogHiPS('https://axel.u-strasbg.fr/HiPSCatService/Simbad', {onClick: 'showTable', name: 'Simbad', color: 'cyan', hoverColor: 'red', shape: drawFunctionFootprint});
aladin.addCatalog(hips);
});
</script>

View File

@@ -231,7 +231,7 @@
A.init.then(() => {
var hipsDir="http://alasky.u-strasbg.fr/CDS_P_Coronelli";
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, expandLayersControl: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
aladin.createImageSurvey('illenoroC', 'illenoroC', hipsDir, 'equatorial', 4, {imgFormat: 'jpg', longitudeReversed: false});
aladin.createImageSurvey('Coronelli', 'Coronelli', hipsDir, 'equatorial', 4, {imgFormat: 'jpg', longitudeReversed: true});
aladin.setImageSurvey('Coronelli');

View File

@@ -23,7 +23,7 @@
});
aladin.addCatalog(A.catalogFromSimbad('M 82', 0.1, {onClick: 'showTable'}));
aladin.addCatalog(A.catalogFromNED('09 55 52.4 +69 40 47', 0.1, {onClick: 'showPopup', shape: 'plus'}));
aladin.addCatalog(A.catalogFromNED('09 55 52.4 +69 40 47', 0.1, {onClick: 'showPopup', shape: 'plus'}));
});
</script>

View File

@@ -29,7 +29,6 @@ A.init.then(() => {
aladin.addCatalog(A.catalogFromURL(vmc_cepheids, {onClick: 'showTable', sourceSize:14, color: '#fff080'}));
aladin.addCatalog(A.catalogFromURL(pessto, {onClick: 'showPopup', sourceSize:14, color: '#00f080'}));
aladin.on('select', (objs) => {

View File

@@ -34,7 +34,7 @@
console.log("Object hovered stopped: ", object, "mouse coords xy: ", xyMouseCoords.x, xyMouseCoords.y);
})
const cat = A.catalogFromVizieR('B/assocdata/obscore', 'M 1', 100, {onClick: 'showTable', limit: 1000});
const cat = A.catalogFromVizieR('B/assocdata/obscore', 'M 1', 10, {onClick: 'showTable', hoverColor: 'purple', limit: 10000});
aladin.addCatalog(cat);
});
</script>

View File

@@ -5,11 +5,10 @@
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script> let aladin;
</script>
<script type="module">
import A from '../src/js/A.js';
var aladin;
A.init.then(() => {
aladin = A.aladin(
'#aladin-lite-div',
@@ -22,8 +21,9 @@
reticleColor: '#00ff00', // change reticle color
reticleSize: 40, // change reticle size
gridOptions: {color: 'pink'},
showCooGrid: true, // set the grid
showCooGrid: false, // set the grid
fullScreen: true,
inertia: false,
showStatusBar: false,
showShareControl: true,
showSettingsControl: true,

View File

@@ -16,7 +16,7 @@
aladin.addCatalog(catalog)
});
aladin.addCatalog(A.catalogFromVizieR("B/assocdata/obscore", "0 +0", 20, {limit: 1000, hoverColor: 'cyan'}))
aladin.addCatalog(A.catalogFromVizieR("B/assocdata/obscore", "0 +0", 20, {onClick: 'showTable', hoverColor: 'yellow', limit: 1000}))
});
</script>
</body>

View File

@@ -0,0 +1,32 @@
<!doctype html>
<html>
<head>
<!--<link rel="stylesheet" href="./layers.css" />-->
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from '../src/js/A.js';
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, survey: 'https://alasky.cds.unistra.fr/DSS/DSSColor/', fov: 180, showContextMenu: true});
// manage URL parameters
let survey1 = aladin.getBaseImageLayer();
survey1.setColormap('magma', {stretch: 'linear'});
let survey2 = aladin.newImageSurvey("CSIRO/P/RACS/low/I");
aladin.setImageLayer(survey2)
survey2.setColormap('rdbu', {stretch: 'linear'});
let survey3 = aladin.newImageSurvey("CSIRO/P/RACS/mid/I");
aladin.setImageLayer(survey3)
survey3.setColormap('cubehelix', {stretch: 'asinh'});
aladin.setImageLayer(survey2);
});
</script>
</body>
</html>

View File

@@ -12,7 +12,7 @@
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, target: "Abell 194", fov: 180, projection: 'AIT', showContextMenu: true});
aladin.setImageSurvey('astron.nl/P/lotss_dr2_high')
A.catalogFromSKAORucio("Abell 194", 90, {onClick: 'showTable'}, (cat) => {
A.catalogFromSKAORucio("Abell 194", 90, {onClick: 'showTable', hoverColor: 'yellow'}, (cat) => {
aladin.addCatalog(cat);
});

View File

@@ -8,15 +8,13 @@
<script type="module">
import A from '../src/js/A.js';
import {AladinUtils} from '../src/js/AladinUtils.js';
A.init.then(() => {
let vertices = AladinUtils.HEALPix.vertices(8, 0n)
let lonlat = AladinUtils.HEALPix.pix2ang(8, 0n)
let ipix = AladinUtils.HEALPix.ang2pix(8, 0.1, 0.4)
console.log(vertices, lonlat, ipix)
let vertices = A.Utils.HEALPix.vertices(Math.pow(2, 3), BigInt(276))
//let lonlat = A.Utils.HEALPix.pix2ang(8, 0n)
//let ipix = A.Utils.HEALPix.ang2pix(8, 0.1, 0.4)
//console.log("vertices", vertices, lonlat, ipix)
console.log(vertices)
})
</script>

View File

@@ -36,7 +36,7 @@
}
if (searchParams.has('showCooGrid')) {
const b = searchParams.get('showCooGrid') === 'true';
aladin.view.setGridConfig({
aladin.setCooGrid({
enabled: b
});
}

View File

@@ -37,7 +37,7 @@
}
if (searchParams.has('showCooGrid')) {
const b = searchParams.get('showCooGrid') === 'true';
aladin.view.setGridConfig({
aladin.setCooGrid({
enabled: b
});
}

View File

@@ -36,7 +36,7 @@
}
if (searchParams.has('showCooGrid')) {
const b = searchParams.get('showCooGrid') === 'true';
aladin.view.setGridConfig({
aladin.setCooGrid({
enabled: b
});
}

View File

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

View File

@@ -17,45 +17,34 @@ pub enum Data<'a> {
I32(Cow<'a, [i32]>),
F32(Cow<'a, [f32]>),
}
use fitsrs::{fits::Fits as FitsData, hdu::data::InMemData};
use std::io::Cursor;
use fitsrs::{
hdu::data::InMemData,
fits::Fits as FitsData,
};
impl<'a> Fits<'a> {
pub fn from_byte_slice(bytes_reader: &'a mut Cursor<&[u8]>) -> Result<Self, JsValue> {
let FitsData { hdu } = FitsData::from_reader(bytes_reader)
.map_err(|_| {
JsValue::from_str(&"Parsing fits error")
})?;
.map_err(|_| JsValue::from_str(&"Parsing fits error"))?;
let header = hdu.get_header();
let xtension = header.get_xtension();
let width = xtension.get_naxisn(1)
let width = xtension
.get_naxisn(1)
.ok_or_else(|| JsValue::from_str("NAXIS1 not found in the fits"))?;
let height = xtension.get_naxisn(2)
let height = xtension
.get_naxisn(2)
.ok_or_else(|| JsValue::from_str("NAXIS2 not found in the fits"))?;
let data = hdu.get_data();
let data = match *data {
InMemData::U8(slice) => {
Data::U8(Cow::Borrowed(slice))
},
InMemData::I16(slice) => {
Data::I16(Cow::Borrowed(slice))
},
InMemData::I32(slice) => {
Data::I32(Cow::Borrowed(slice))
},
InMemData::U8(slice) => Data::U8(Cow::Borrowed(slice)),
InMemData::I16(slice) => Data::I16(Cow::Borrowed(slice)),
InMemData::I32(slice) => Data::I32(Cow::Borrowed(slice)),
InMemData::I64(slice) => {
let data = slice.iter().map(|v| *v as i32).collect();
Data::I32(Cow::Owned(data))
},
InMemData::F32(slice) => {
Data::F32(Cow::Borrowed(slice))
},
}
InMemData::F32(slice) => Data::F32(Cow::Borrowed(slice)),
InMemData::F64(slice) => {
let data = slice.iter().map(|v| *v as f32).collect();
Data::F32(Cow::Owned(data))
@@ -66,8 +55,8 @@ impl<'a> Fits<'a> {
// Tile size
size: Vector2::new(*width as i32, *height as i32),
// Allocation info of the layout
data
// Allocation info of the layout
data,
})
}
@@ -121,14 +110,14 @@ impl<'a> Fits<'a> {
// Tile size
size: Vector2::new(*width as i32, *height as i32),
// Allocation info of the layout
// Allocation info of the layout
data
})
}
}*/
use crate::Texture2DArray;
use crate::image::Image;
use crate::Texture2DArray;
impl Image for Fits<'_> {
fn tex_sub_image_3d(
&self,
@@ -138,7 +127,7 @@ impl Image for Fits<'_> {
offset: &Vector3<i32>,
) -> Result<(), JsValue> {
match &self.data {
Data::U8(data) => {
Data::U8(data) => {
let view = unsafe { R8UI::view(&data) };
textures[offset.z as usize]
.bind()
@@ -150,7 +139,7 @@ impl Image for Fits<'_> {
Some(view.as_ref()),
);
}
Data::I16(data) => {
Data::I16(data) => {
let view = unsafe { R16I::view(&data) };
textures[offset.z as usize]
.bind()
@@ -162,7 +151,7 @@ impl Image for Fits<'_> {
Some(view.as_ref()),
);
}
Data::I32(data) => {
Data::I32(data) => {
let view = unsafe { R32I::view(&data) };
textures[offset.z as usize]
.bind()
@@ -174,7 +163,7 @@ impl Image for Fits<'_> {
Some(view.as_ref()),
);
}
Data::F32(data) => {
Data::F32(data) => {
let view = unsafe { R32F::view(&data) };
textures[offset.z as usize]
.bind()
@@ -192,8 +181,8 @@ impl Image for Fits<'_> {
}
}
use wasm_bindgen::JsValue;
use crate::image::format::ImageFormat;
use wasm_bindgen::JsValue;
pub trait FitsImageFormat: ImageFormat {
const BITPIX: i8;
@@ -205,7 +194,7 @@ impl FitsImageFormat for R32F {
}
#[cfg(feature = "webgl2")]
use crate::image::{R16I, R32I, R8UI, R64F};
use crate::image::{R16I, R32I, R64F, R8UI};
#[cfg(feature = "webgl2")]
impl FitsImageFormat for R64F {
const BITPIX: i8 = -64;

View File

@@ -27,12 +27,12 @@ impl Texture2DArray {
tex_params: &'static [(u32, u32)],
) -> Result<Texture2DArray, JsValue> {
let textures: Result<Vec<_>, _> = (0..num_slices)
.map(|_| {
Texture2D::create_empty_with_format::<F>(gl, width, height, tex_params)
})
.map(|_| Texture2D::create_empty_with_format::<F>(gl, width, height, tex_params))
.collect();
Ok(Texture2DArray { textures: textures? })
Ok(Texture2DArray {
textures: textures?,
})
}
}

View File

@@ -606,11 +606,11 @@ impl App {
for rsc in rscs_received {
match rsc {
Resource::Tile(tile) => {
if !has_camera_moved {
if !_has_camera_zoomed {
if let Some(survey) =
self.layers.get_mut_hips_from_cdid(&tile.get_hips_cdid())
{
let cfg = survey.get_config();
let cfg = survey.get_config_mut();
if cfg.get_format() == tile.format {
let delta_depth = cfg.delta_depth();
@@ -649,6 +649,63 @@ impl App {
} else {
Some(image)
};
use al_core::image::ImageType;
use fitsrs::fits::Fits;
use std::{io::Cursor, rc::Rc};
if let Some(image) = image.as_ref() {
match &*image.lock().unwrap_abort() {
Some(ImageType::FitsImage {
raw_bytes: raw_bytes_buf,
}) => {
// check if the metadata has not been set
if !cfg.fits_metadata {
let num_bytes =
raw_bytes_buf.length() as usize;
let mut raw_bytes = vec![0; num_bytes];
raw_bytes_buf.copy_to(&mut raw_bytes[..]);
let mut bytes_reader =
Cursor::new(raw_bytes.as_slice());
let Fits { hdu } =
Fits::from_reader(&mut bytes_reader)
.map_err(|_| {
JsValue::from_str(
"Parsing fits error",
)
})?;
let header = hdu.get_header();
let bscale = if let Some(
fitsrs::card::Value::Float(bscale),
) = header.get(b"BSCALE ")
{
*bscale as f32
} else {
1.0
};
let bzero = if let Some(
fitsrs::card::Value::Float(bzero),
) = header.get(b"BZERO ")
{
*bzero as f32
} else {
0.0
};
let blank = if let Some(
fitsrs::card::Value::Float(blank),
) = header.get(b"BLANK ")
{
*blank as f32
} else {
std::f32::NAN
};
cfg.set_fits_metadata(bscale, bzero, blank);
}
}
_ => (),
}
}
survey.add_tile(&cell, image, time_req)?;
self.request_redraw = true;
@@ -821,6 +878,10 @@ impl App {
}
}
pub(crate) fn draw_grid_labels(&mut self) -> Result<(), JsValue> {
self.grid.draw_labels(&self.camera)
}
pub(crate) fn draw(&mut self, force_render: bool) -> Result<(), JsValue> {
/*let scene_redraw = self.rendering | force_render;
let mut ui = self.ui.lock();
@@ -1497,6 +1558,10 @@ impl App {
self.request_redraw = true;
}
pub(crate) fn set_inertia(&mut self, inertia: bool) {
*self.disable_inertia.borrow_mut() = !inertia;
}
/*pub(crate) fn project_line(&self, lon1: f64, lat1: f64, lon2: f64, lat2: f64) -> Vec<Vector2<f64>> {
let v1: Vector3<f64> = LonLatT::new(ArcDeg(lon1).into(), ArcDeg(lat1).into()).vector();
let v2: Vector3<f64> = LonLatT::new(ArcDeg(lon2).into(), ArcDeg(lat2).into()).vector();

View File

@@ -11,6 +11,7 @@ use crate::healpix::cell::HEALPixCell;
use crate::healpix::coverage::HEALPixCoverage;
use crate::math::angle::ToAngle;
use crate::math::{projection::coo_space::XYZWModel, projection::domain::sdf::ProjDef};
use al_core::log::console_log;
use al_core::{info, inforec, log};
use cgmath::{Matrix4, Vector2};
@@ -380,6 +381,8 @@ impl CameraViewPort {
}
};
//console_log(&format!("clip factor {:?}", self.aperture));
// Project this vertex into the screen
self.moved = true;
self.zoomed = true;

View File

@@ -2,6 +2,7 @@ pub mod label;
pub mod meridian;
pub mod parallel;
use crate::grid::parallel::Parallel;
use crate::math::projection::coo_space::XYScreen;
use crate::Abort;
@@ -31,6 +32,9 @@ pub struct ProjetedGrid {
fmt: angle::SerializeFmt,
line_style: line::Style,
meridians: Vec<Meridian>,
parallels: Vec<Parallel>,
}
use crate::shader::ShaderManager;
@@ -40,6 +44,8 @@ use crate::renderable::line::RasterizedLineRenderer;
use crate::renderable::text::TextRenderManager;
use web_sys::HtmlElement;
use self::meridian::Meridian;
impl ProjetedGrid {
pub fn new(aladin_div: &HtmlElement) -> Result<ProjetedGrid, JsValue> {
let text_renderer = TextRenderManager::new(aladin_div)?;
@@ -56,6 +62,8 @@ impl ProjetedGrid {
let line_style = line::Style::None;
let fmt = angle::SerializeFmt::DMS;
let thickness = 2.0;
let meridians = Vec::new();
let parallels = Vec::new();
let grid = ProjetedGrid {
color,
@@ -66,6 +74,8 @@ impl ProjetedGrid {
thickness,
text_renderer,
meridians,
parallels,
fmt,
};
// Initialize the vertices & labels
@@ -126,9 +136,9 @@ impl ProjetedGrid {
if let Some(enabled) = enabled {
self.enabled = enabled;
if !self.enabled {
/*if !self.enabled {
self.text_renderer.clear_text_canvas();
}
}*/
}
Ok(())
@@ -147,7 +157,7 @@ impl ProjetedGrid {
let step_line_px = max_dim_px * 0.2;
// update meridians
let meridians = {
self.meridians = {
// Select the good step with a binary search
let step_lon_precised =
(bbox.get_lon_size() as f64) * step_line_px / (camera.get_width() as f64);
@@ -173,7 +183,7 @@ impl ProjetedGrid {
meridians
};
let parallels = {
self.parallels = {
let step_lat_precised =
(bbox.get_lat_size() as f64) * step_line_px / (camera.get_height() as f64);
let step_lat = select_fixed_step(step_lat_precised);
@@ -196,11 +206,12 @@ impl ProjetedGrid {
};
// update the line buffers
let paths = meridians
let paths = self
.meridians
.iter()
.map(|meridian| meridian.get_lines_vertices())
.chain(
parallels
self.parallels
.iter()
.map(|parallel| parallel.get_lines_vertices()),
)
@@ -213,12 +224,16 @@ impl ProjetedGrid {
let m = camera.get_screen_size().magnitude();
rasterizer.add_stroke_paths(paths, self.thickness, &self.color, &self.line_style);
// update labels
if self.show_labels {
let labels = meridians
Ok(())
}
pub fn draw_labels(&mut self, camera: &CameraViewPort) -> Result<(), JsValue> {
if self.enabled && self.show_labels {
let labels = self
.meridians
.iter()
.filter_map(|m| m.get_label())
.chain(parallels.iter().filter_map(|p| p.get_label()));
.chain(self.parallels.iter().filter_map(|p| p.get_label()));
let dpi = camera.get_dpi();
self.text_renderer.begin();

View File

@@ -73,6 +73,7 @@ extern "C" {
#[macro_use]
mod utils;
use al_core::log::console_log;
use math::projection::*;
use renderable::coverage::moc::MOC;
//use votable::votable::VOTableWrapper;
@@ -442,7 +443,7 @@ impl WebClient {
/// * `green` - Green amount (between 0.0 and 1.0)
/// * `blue` - Blue amount (between 0.0 and 1.0)
/// * `alpha` - Alpha amount (between 0.0 and 1.0)
#[wasm_bindgen(js_name = setGridConfig)]
#[wasm_bindgen(js_name = setGridOptions)]
pub fn set_grid_cfg(&mut self, cfg: JsValue) -> Result<(), JsValue> {
let cfg = serde_wasm_bindgen::from_value(cfg)?;
@@ -484,6 +485,13 @@ impl WebClient {
Ok(())
}
#[wasm_bindgen(js_name = setInertia)]
pub fn set_inertia(&mut self, inertia: bool) -> Result<(), JsValue> {
self.app.set_inertia(inertia);
Ok(())
}
/// Set the absolute orientation of the view
///
/// # Arguments
@@ -927,6 +935,11 @@ impl WebClient {
self.app.is_rendering()
}
#[wasm_bindgen(js_name = drawGridLabels)]
pub fn draw_grid_labels(&mut self) -> Result<(), JsValue> {
self.app.draw_grid_labels()
}
#[wasm_bindgen(js_name = parseVOTable)]
pub fn parse_votable(&mut self, s: &str) -> Result<JsValue, JsValue> {
/*let votable: VOTableWrapper<votable::impls::mem::InMemTableDataRows> =

View File

@@ -1,4 +1,5 @@
use super::Renderer;
use al_core::log::console_log;
use web_sys::CanvasRenderingContext2d;
pub struct TextRenderManager {
@@ -25,7 +26,7 @@ impl TextRenderManager {
pub fn new(aladin_div: &HtmlElement) -> Result<Self, JsValue> {
let canvas = aladin_div
// Inside it, retrieve the canvas
.get_elements_by_class_name("aladin-gridCanvas")
.get_elements_by_class_name("aladin-catalogCanvas")
.get_with_index(0)
.unwrap_abort()
.dyn_into::<web_sys::HtmlCanvasElement>()?;
@@ -67,6 +68,7 @@ impl TextRenderManager {
angle: A,
) -> Result<(), JsValue> {
self.ctx.save();
self.ctx
.translate(screen_pos.x as f64, screen_pos.y as f64)?;
@@ -80,37 +82,19 @@ impl TextRenderManager {
Ok(())
}
pub fn draw(
&mut self,
_camera: &CameraViewPort,
_color: &ColorRGBA,
_scale: f32,
) -> Result<(), JsValue> {
Ok(())
}
pub fn clear_text_canvas(&mut self) {
self.ctx.clear_rect(
0_f64,
0_f64,
self.canvas.width() as f64,
self.canvas.height() as f64,
);
}
}
impl Renderer for TextRenderManager {
fn begin(&mut self) {
self.ctx = self
.canvas
.get_context("2d")
.unwrap_abort()
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap_abort();
self.clear_text_canvas();
//self.clear_text_canvas();
// Clear the Aladin Lite 2d canvas
// This canvas is where catalogs, grid labels, Hpx grid are drawn
/*self.ctx.clear_rect(
0_f64,
0_f64,
self.canvas.width() as f64,
self.canvas.height() as f64,
);*/
// reset the font and color
self.ctx

View File

@@ -384,29 +384,20 @@ impl ImageSurveyTextures {
&mut self.base_textures[idx as usize]
};*/
if let Some(image) = image {
send_to_gpu(cell, texture, image, &self.texture_2d_array, &self.config)?;
// Once the texture has been received in the GPU
texture.append(
cell, // The tile cell
&self.config,
false,
);
} else {
send_to_gpu(
cell,
texture,
self.config.get_default_image(),
&self.texture_2d_array,
&self.config,
)?;
// Once the texture has been received in the GPU
texture.append(
cell, // The tile cell
&self.config,
true,
);
};
let missing = image.is_none();
send_to_gpu(
cell,
texture,
image,
&self.texture_2d_array,
&mut self.config,
)?;
texture.append(
cell, // The tile cell
&self.config,
missing,
);
self.available_tiles_during_frame = true;
//self.ready = true;
@@ -638,9 +629,9 @@ impl ImageSurveyTextures {
fn send_to_gpu<I: Image>(
cell: &HEALPixCell,
texture: &Texture,
image: I,
image: Option<I>,
texture_array: &Texture2DArray,
cfg: &HiPSConfig,
cfg: &mut HiPSConfig,
) -> Result<(), JsValue> {
// Index of the texture in the total set of textures
let texture_idx = texture.idx();
@@ -672,7 +663,12 @@ fn send_to_gpu<I: Image>(
idx_slice,
);
image.tex_sub_image_3d(&texture_array, &offset)
if let Some(image) = image {
image.tex_sub_image_3d(&texture_array, &offset)
} else {
cfg.get_default_image()
.tex_sub_image_3d(&texture_array, &offset)
}
}
impl SendUniforms for ImageSurveyTextures {

View File

@@ -156,6 +156,7 @@ pub struct HiPSConfig {
// TODO: store this values in the ImageSurvey
// These are proper to the survey (FITS one) and not
// to a specific survey color
pub fits_metadata: bool,
pub scale: f32,
pub offset: f32,
pub blank: f32,
@@ -180,7 +181,7 @@ use wasm_bindgen::JsValue;
const NUM_TEXTURES_BY_SIDE_SLICE: i32 = 8;
const NUM_TEXTURES_BY_SLICE: i32 = NUM_TEXTURES_BY_SIDE_SLICE * NUM_TEXTURES_BY_SIDE_SLICE;
const NUM_SLICES: i32 = 2;
const NUM_SLICES: i32 = 1;
impl HiPSConfig {
/// Define a HiPS configuration
@@ -328,6 +329,7 @@ impl HiPSConfig {
is_allsky,
fits_metadata: false,
scale: 1.0,
offset: 0.0,
blank: -1.0, // by default, set it to -1
@@ -449,6 +451,7 @@ impl HiPSConfig {
self.scale = bscale;
self.offset = bzero;
self.blank = blank;
self.fits_metadata = true;
}
#[inline(always)]

View File

@@ -2,7 +2,6 @@
position: relative;
border: 0px solid #ddd;
/* SVG inside divs add a 4px height: https://stackoverflow.com/questions/75751593/why-there-is-additional-4px-height-for-div-when-there-is-svg-inside-it */
/* disable x swipe on chrome, firefox */
/* see. https://stackoverflow.com/questions/30636930/disable-web-page-navigation-on-swipeback-and-forward */
overscroll-behavior-x: none;
@@ -17,12 +16,6 @@
top: 0;
}
.aladin-gridCanvas {
position: absolute;
left: 0;
top: 0;
}
.aladin-catalogCanvas {
position: absolute;
left: 0;
@@ -54,18 +47,6 @@
padding-top: 58.45%; /* aspect ratio of the background image */
}
.aladin-col {
float: left;
width: 45.00%;
margin-right: 5.0%;
}
/* Clear floats after the columns */
.aladin-row:after {
content: "";
display: table;
clear: both;
}
.aladin-clipboard::before {
content: ' 📋';
cursor:pointer;
@@ -448,7 +429,7 @@ canvas {
}
.aladin-input-text.aladin-dark-theme.search {
width: 14rem;
width: 15rem;
text-shadow: 0px 0px 2px #000;
}
@@ -463,6 +444,7 @@ canvas {
background-image:none;
text-indent: 0rem;
font-size: 1rem;
line-height: 1.2rem;
}
.aladin-input-text.search.aladin-unknownObject {
@@ -752,6 +734,10 @@ canvas {
box-shadow:inset 1px 1px 0px 0px #fff;
background-color: rgba(0, 0, 0, 0.7);
margin: 0;
box-sizing: content-box;
line-height: normal;
}
.aladin-context-menu .aladin-context-menu-item:first-of-type {
@@ -838,6 +824,7 @@ canvas {
border-radius: 5px;
font-family: monospace;
font-size: 1rem;
box-sizing: border-box;
}
.aladin-input-text.aladin-dark-theme, .aladin-input-number.aladin-dark-theme {
@@ -867,18 +854,20 @@ canvas {
/* Remove focus outline */
/* Remove IE arrow */
}
.aladin-input-select option {
color: inherit;
background-color: #320a28;
}
.aladin-input-select:focus {
outline: none;
}
.aladin-input-select::-ms-expand {
display: none;
}
/* Frames */
.aladin-input-color {
appearance: none;
@@ -1118,6 +1107,19 @@ 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-location {
position: absolute;
top: 0.2rem;
@@ -1136,6 +1138,8 @@ canvas {
bottom: 0.2rem;
left: 0.2rem;
background-color: red;
font-family: monospace;
font-size: 1rem;
@@ -1143,6 +1147,16 @@ canvas {
line-height: 1.7rem;
}
.aladin-fov .aladin-zoom-in {
margin-right: 0;
border-right: none;
border-radius: 5px 0px 0px 5px;
}
.aladin-fov .aladin-zoom-out {
border-radius: 0px 5px 5px 0px;
}
.aladin-status-bar {
border-radius: 3px;
padding: 0.4rem;

View File

@@ -33,6 +33,7 @@ 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 { Catalog } from "./Catalog.js";
import { ProgressiveCat } from "./ProgressiveCat.js";
import { Source } from "./Source.js";
@@ -146,7 +147,7 @@ A.imageFITS = function (url, options) {
* @memberof A
* @param {number} ra - Right Ascension (RA) coordinate in degrees.
* @param {number} dec - Declination (Dec) coordinate in degrees.
* @param {*} [data] - Additional data associated with the source.
* @param {Object} [data] - Additional data associated with the source.
* @param {SourceOptions} [options] - Options for configuring the source object.
* @returns {Source} A celestial source object.
* @example
@@ -165,7 +166,7 @@ A.source = function (ra, dec, data, options) {
* @param {number} ra - Right Ascension (RA) coordinate in degrees.
* @param {number} dec - Declination (Dec) coordinate in degrees.
* @param {MarkerOptions} [options] - Options for configuring the marker.
* @param {*} [data] - Additional data associated with the marker.
* @param {Object} [data] - Additional data associated with the marker.
* @returns {Source} A marker source object.
* @example
* const markerObj = A.marker(180.0, 30.0, data, options);
@@ -183,10 +184,11 @@ A.marker = function (ra, dec, options, data) {
* @memberof A
* @name polygon
*
* @param {Array} raDecArray - Array of celestial coordinates representing the vertices of the polygon.
* Each element should be an object with properties `ra` (Right Ascension) in degrees and `dec` (Declination) in degrees.
* @param {Object} options - Options for configuring the polygon.
* @param {Array.<number[]>} radecArray - right-ascension/declination 2-tuple array describing the polyline's vertices in degrees
* @param {ShapeOptions} options - Options for configuring the polygon
* @throws {string} Throws an error if the number of vertices is less than 3.
*
* @returns {Polyline}
*/
A.polygon = function (raDecArray, options) {
const numVertices = raDecArray.length;
@@ -211,15 +213,16 @@ A.polygon = function (raDecArray, options) {
};
/**
* Creates a polyline object using an array of celestial coordinates (RA, Dec).
* Creates a polyline shape
*
* @function
* @memberof A
* @name polyline
*
* @param {Array} raDecArray - Array of celestial coordinates representing the vertices of the polyline.
* Each element should be an object with properties `ra` (Right Ascension) in degrees and `dec` (Declination) in degrees.
* @param {Object} options - Options for configuring the polyline.
* @param {Array.<number[]>} radecArray - right-ascension/declination 2-tuple array describing the polyline's vertices in degrees
* @param {ShapeOptions} options - Options for configuring the polyline.
*
* @returns {Polyline}
*/
A.polyline = function (raDecArray, options) {
return new Polyline(raDecArray, options);
@@ -227,7 +230,7 @@ A.polyline = function (raDecArray, options) {
/**
* Creates a circle object
* Creates a circle shape
*
* @function
* @memberof A
@@ -237,14 +240,15 @@ A.polyline = function (raDecArray, options) {
* @param {number} dec - Declination (Dec) coordinate of the center in degrees.
* @param {number} radiusDeg - Radius in degrees.
* @param {Object} options - Options for configuring the circle.
* @param {ShapeOptions} options - Options for configuring the circle.
* @returns {Circle}
*/
A.circle = function (ra, dec, radiusDeg, options) {
return new Circle([ra, dec], radiusDeg, options);
};
/**
* Creates a ellipse object
* Creates an ellipse shape
*
* @function
* @memberof A
@@ -256,12 +260,55 @@ A.circle = function (ra, dec, radiusDeg, options) {
* @param {number} radiusDecDeg - the radius along the dec axis in degrees
* @param {number} rotationDeg - the rotation angle in degrees
* @param {Object} options - Options for configuring the ellipse.
* @param {ShapeOptions} options - Options for configuring the ellipse.
* @returns {Ellipse}
*/
A.ellipse = function (ra, dec, radiusRaDeg, radiusDecDeg, rotationDeg, options) {
return new Ellipse([ra, dec], radiusRaDeg, radiusDecDeg, rotationDeg, options);
};
/**
* Creates a line shape
*
* @function
* @memberof A
* @name line
*
* @param {number} ra1 - Right Ascension (RA) coordinate of the center in degrees.
* @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.
*
* @returns {Line}
*/
A.line = function (ra1, dec1, ra2, dec2, frame, options) {
return new Line(ra1, dec1, ra2, dec2, frame, options);
};
/**
* Creates a vector shape
*
* @function
* @memberof A
* @name vector
*
* @param {number} ra1 - Right Ascension (RA) coordinate of the center in degrees.
* @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) {
options['arrow'] = true;
return A.line(ra1, dec1, ra2, dec2, frame, options);
}
/**
* Creates a graphic overlay on the Aladin Lite view.
*
@@ -317,27 +364,68 @@ A.coo = function (longitude, latitude, prec) {
return new Coo(longitude, latitude, prec);
};
// API
A.footprint = function(shapes, source) {
return new Footprint(shapes, source);
};
// API
/**
* Parse shapes from a STC-S string
*
* @function
* @memberof A
* @name footprintsFromSTCS
*
* @param {string} stcs - The STC-S string describing the shapes
* @param {ShapeOptions} [options] - Options for the shape
* @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);
return footprints;
}
// API
A.MOCFromURL = function (url, options, successCallback) {
/**
* Creates a new MOC (Multi-Order-Coverage) from an url
*
* @function
* @memberof A
* @name MOCFromURL
*
* @param {string} url - The url to the MOC (e.g. stored as FITS file)
* @param {MOCOptions} [options] - Display options for the MOC
* @param {function} [successCallback] - Callback function when the MOC loads
* @param {function} [errorCallback] - Callback function when the MOC fails loading
* @returns {MOC} Returns a new MOC object
*/
A.MOCFromURL = function (url, options, successCallback, errorCallback) {
var moc = new MOC(options);
moc.parse(url, successCallback);
moc.parse(url, successCallback, errorCallback);
return moc;
};
// API
/**
* Creates a new MOC (Multi-Order-Coverage) from a JSON-like dictionary (javascript Object)
*
* @function
* @memberof A
* @name MOCFromJSON
*
* @param {Object} jsonMOC - The MOC stores as a JSON-like dictionary
* @param {MOCOptions} [options] - Display options for the MOC
* @param {function} [successCallback] - Callback function when the MOC loads
* @param {function} [errorCallback] - Callback function when the MOC fails loading
* @returns {MOC} Returns a new MOC object
*
* @example
* var json = {"3":[517],
* "4":[2065,2066,2067,2112,2344,2346,2432],
* "5":[8221,8257,8258,8259,8293,8304,8305,8307,8308,8452,8456,9346,9352,9354,9736],
* "6":[32861,32862,32863,32881,32882,32883,32892,32893,33025,33026,33027,33157,33168,33169,33171,
* 33181,33224,33225,33227,33236,33240,33812,33816,33828,33832,37377,37378,37379,37382,37388,
* 37390,37412,37414,37420,37422,37562,38928,38930,38936,38948,38952],
* "7":[131423,131439,131443,131523,131556,131557,131580,131581,132099,132612,132613,132624,132625,132627,132637,
* 132680,132681,132683,132709,132720,132721,132904,132905,132948,132952,132964,132968,133008,133009,133012,135252,135256,135268,135316,135320,135332,135336,148143,148152,148154,149507,149520
* ,149522,149523,149652,149654,149660,149662,149684,149686,149692,149694,149695,150120,150122,150208,150210,150216,150218,150240,150242,150243,155748,155752,155796,155800,155812,155816]};
* var moc = A.MOCFromJSON(json, {opacity: 0.25, color: 'magenta', lineWidth: 3});
* aladin.addMOC(moc);
*/
A.MOCFromJSON = function (jsonMOC, options, successCallback, errorCallback) {
var moc = new MOC(options);
moc.parse(jsonMOC, successCallback, errorCallback);
@@ -345,14 +433,44 @@ A.MOCFromJSON = function (jsonMOC, options, successCallback, errorCallback) {
return moc;
};
// API
A.MOCFromCircle = function (circle, options, successCallback, errorCallback) {
/**
* Creates a new MOC (Multi-Order-Coverage) from an object describing a cone on the sky
*
* @function
* @memberof A
* @name MOCFromCone
*
* @param {Object} circle - A object describing a cone in the sky
* @param {number} circle.ra - Right-ascension of the circle's center (in deg)
* @param {number} circle.dec - Declination of the circle's center (in deg)
* @param {number} circle.radius - Radius of the circle (in deg)
* @param {MOCOptions} [options] - Display options for the MOC
* @param {function} [successCallback] - Callback function when the MOC loads
* @param {function} [errorCallback] - Callback function when the MOC fails loading
* @returns {MOC} Returns a new MOC object
*/
A.MOCFromCone = function (circle, options, successCallback, errorCallback) {
var moc = new MOC(options);
moc.parse(circle, successCallback, errorCallback);
return moc;
};
/**
* Creates a new MOC (Multi-Order-Coverage) from an object describing a polygon on the sky
*
* @function
* @memberof A
* @name MOCFromPolygon
*
* @param {Object} polygon - A object describing a polygon in the sky
* @param {number[]} polygon.ra - Right-ascensions of the polygon's vertices (in deg)
* @param {number[]} polygon.dec - Declination of the polygon's vertices (in deg)
* @param {MOCOptions} [options] - Display options for the MOC
* @param {function} [successCallback] - Callback function when the MOC loads
* @param {function} [errorCallback] - Callback function when the MOC fails loading
* @returns {MOC} Returns a new MOC object
*/
A.MOCFromPolygon= function (polygon, options, successCallback, errorCallback) {
var moc = new MOC(options);
moc.parse(polygon, successCallback, errorCallback);
@@ -435,18 +553,24 @@ A.catalogFromURL = function (url, options, successCallback, errorCallback, usePr
options.url = url;
var catalog = A.catalog(options);
const processVOTable = function (table) {
let {sources, footprints, fields, type} = table;
let {sources, fields} = table;
catalog.setFields(fields);
if (catalog.type === 'ObsCore') {
// The fields corresponds to obscore ones
// Set the name of the catalog to be ObsCore:<catalog name>
catalog.name = "ObsCore:" + url;
}
catalog.addFootprints(footprints)
catalog.addSources(sources);
if ('s_region' in fields && typeof catalog.shape !== 'function') {
// set the shape
catalog.setShape((s) => {
if (!s.data.s_region)
return;
const shapes = A.footprintsFromSTCS(s.data.s_region, options)
let fp = new Footprint(shapes, s);
fp.setColor(catalog.color);
return fp;
})
}
if (successCallback) {
successCallback(catalog);
}
@@ -558,7 +682,7 @@ A.catalogFromSimbad = function (target, radius, options, successCallback, errorC
}).then((coo) => {
const url = URLBuilder.buildSimbadCSURL(coo.lon, coo.lat, radius, options)
const processVOTable = function (table) {
let {sources, footprints, fields, type} = table;
let {sources, fields} = table;
cat.setFields(fields);
if (cat.type === 'ObsCore') {
@@ -567,7 +691,6 @@ A.catalogFromSimbad = function (target, radius, options, successCallback, errorC
cat.name = "ObsCore:" + url;
}
cat.addFootprints(footprints)
cat.addSources(sources);
if (successCallback) {

View File

@@ -40,6 +40,7 @@ import { MeasurementTable } from "./MeasurementTable.js";
import { ImageSurvey } from "./ImageSurvey.js";
import { Coo } from "./libs/astro/coo.js";
import { CooConversion } from "./CooConversion.js";
import { MocServer } from './MocServer';
import { ProjectionEnum } from "./ProjectionEnum.js";
@@ -49,6 +50,7 @@ import { ImageFITS } from "./ImageFITS.js";
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
import { SAMPConnector } from "./vo/samp.js";
import { Reticle } from "./Reticle.js";
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
// GUI
import { AladinLogo } from "./gui/AladinLogo.js";
@@ -76,6 +78,10 @@ import { CooFrame } from './gui/Input/CooFrame';
* @property {string} [survey="CDS/P/DSS2/color"] URL or ID of the survey to use
* @property {string[]} [surveyUrl]
* Array of URLs for the survey images. This replaces the survey parameter.
* @property {Object[]|string[]} [hipsList] A list of predefined HiPS for the Aladin instance.
* This option is used for searching for a HiPS in a list of surveys
* This list can have string item (either a CDS ID or an HiPS url) or an object that describes the HiPS
* more exhaustively. See the example below to see the different form that this item can have to describe a HiPS.
* @property {string} [target="0 +0"] - Target coordinates for the initial view.
* @property {CooFrame} [cooFrame="J2000"] - Coordinate frame.
* @property {number} [fov=60] - Field of view in degrees.
@@ -85,6 +91,8 @@ import { CooFrame } from './gui/Input/CooFrame';
* This element belongs to the FoV UI thus its CSS class is `aladin-fov`
* @property {boolean} [showLayersControl=true] - Whether to show the layers control toolbar.
* CSS class for that button is `aladin-stack-control`
* @property {boolean} [expandLayersControl=false] - Whether to show the stack box at starting
* CSS class for the stack box is `aladin-stack-box`
* @property {boolean} [showFullscreenControl=true] - Whether to show the fullscreen control toolbar.
* CSS class for that button is `aladin-fullScreen-control`
* @property {boolean} [showSimbadPointerControl=false] - Whether to show the Simbad pointer control toolbar.
@@ -132,7 +140,53 @@ import { CooFrame } from './gui/Input/CooFrame';
* @property {boolean} [samp=false] - Whether to enable SAMP (Simple Application Messaging Protocol).
* @property {boolean} [realFullscreen=false] - Whether to use real fullscreen mode.
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
*/
* @example
* let aladin = A.aladin({
target: 'galactic center',
fov: 10,
hipsList: [
// url
"https://alaskybis.unistra.fr/DSS/DSSColor",
// ID from HiPS list
"CDS/P/2MASS/color",
// Not full HiPS described
{
name: 'DESI Legacy Surveys color (g, r, i, z)',
id: 'CDS/P/DESI-Legacy-Surveys/DR10/color',
},
// Fully described HiPS
{
name: "DECaPS DR2 color",
url: "https://alasky.cds.unistra.fr/DECaPS/DR2/CDS_P_DECaPS_DR2_color/",
properties: {
creatorDid: "ivo://CDS/P/DECaPS/DR2/color",
maxOrder: 11,
cooFrame: "equatorial",
tileSize: 512,
imgFormat: 'png',
},
},
// HiPS with options
{
name: "SDSS9 band-g",
id: "P/SDSS9/g",
properties: {
creatorDid: "ivo://CDS/P/SDSS9/g",
maxOrder: 10,
tileSize: 512,
numBitsPerPixel: 16,
imgFormat: 'fits',
cooFrame: 'equatorial',
},
options: {
minCut: 0,
maxCut: 1.8,
stretch: 'linear',
colormap: "redtemperature",
}
}
]
})*/
/**
* @typedef {Object} CircleSelection
@@ -216,9 +270,16 @@ export let Aladin = (function () {
}
// merge with default options
var options = {
gridOptions: {}
};
var options = {};
for (var key in Aladin.DEFAULT_OPTIONS) {
if (requestedOptions[key] !== undefined) {
options[key] = requestedOptions[key];
}
else {
options[key] = Aladin.DEFAULT_OPTIONS[key];
}
}
// 'gridOptions' is an object, so it need it own loop
if ('gridOptions' in requestedOptions) {
@@ -228,14 +289,7 @@ export let Aladin = (function () {
}
}
}
for (var key in Aladin.DEFAULT_OPTIONS) {
if (requestedOptions[key] !== undefined) {
options[key] = requestedOptions[key];
}
else {
options[key] = Aladin.DEFAULT_OPTIONS[key];
}
}
for (var key in requestedOptions) {
if (Aladin.DEFAULT_OPTIONS[key] === undefined) {
options[key] = requestedOptions[key];
@@ -271,6 +325,7 @@ export let Aladin = (function () {
// Grid
let gridOptions = options.gridOptions;
// color and opacity can be defined by two variables. The item in gridOptions
// should take precedence.
gridOptions["color"] = options.gridOptions.color || options.gridColor;
@@ -278,6 +333,7 @@ export let Aladin = (function () {
if (options && options.showCooGrid) {
gridOptions.enabled = true;
}
this.setCooGrid(gridOptions);
this.gotoObject(options.target, undefined);
@@ -332,6 +388,84 @@ export let Aladin = (function () {
this.setBaseImageLayer(url);
}
let hipsList = [].concat(options.hipsList);
const fillHiPSCache = () => {
for (var survey of hipsList) {
let id, url, name;
let cachedSurvey = {};
if (typeof survey === "string") {
try {
url = new URL(survey).href;
} catch(e) {
id = survey;
}
name = url || id;
} else if (survey instanceof Object) {
if (survey.id) {
id = survey.id;
}
if (survey.url) {
url = survey.url;
}
name = survey.name || survey.id || survey.url;
if (id && url) {
console.warn('Both "CDS ID" and url are given for ', survey, '. ID is chosen.')
url = null;
}
if (survey.properties) {
cachedSurvey = {...cachedSurvey, ...survey.properties}
}
if (survey.options) {
cachedSurvey = {...cachedSurvey, ...survey.options}
}
} else {
console.warn('unable to parse the survey list item: ', survey)
continue;
}
if (id) {
cachedSurvey['id'] = id;
}
if (url) {
cachedSurvey['url'] = url;
}
if (name) {
cachedSurvey['name'] = name;
}
// at least id or url is defined
let key = id || url;
if (!(key in ImageSurvey.cache)) {
ImageSurvey.cache[key] = cachedSurvey
}
}
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(this.aladinDiv);
}
if (hipsList.length === 0) {
MocServer.getAllHiPSes()
.then((HiPSes) => {
HiPSes.forEach((h) => {
hipsList.push({
id: h.ID,
name: h.obs_title
})
});
fillHiPSCache();
});
} else {
fillHiPSCache();
}
this.view.showCatalog(options.showCatalog);
// FullScreen toolbar icon
@@ -366,6 +500,10 @@ export let Aladin = (function () {
this.samp = new SAMPConnector(this);
}
if (options.inertia !== undefined) {
this.wasm.setInertia(options.inertia)
}
this._setupUI(options);
};
@@ -405,6 +543,7 @@ export let Aladin = (function () {
if (!options.showLayersControl) {
stack._hide();
}
// Add the simbad pointer control
if (!options.showSimbadPointerControl) {
simbad._hide();
@@ -436,6 +575,10 @@ export let Aladin = (function () {
this.addUI(new FullScreenActionButton(self))
}
if (options.expandLayersControl) {
stack.toggle();
}
this._applyMediaQueriesUI();
}
@@ -487,15 +630,19 @@ export let Aladin = (function () {
Aladin.wasmLibs = {};
Aladin.DEFAULT_OPTIONS = {
survey: ImageSurvey.DEFAULT_SURVEY_ID,
// surveys suggestion list
hipsList: [],
//surveyUrl: ["https://alaskybis.unistra.fr/DSS/DSSColor", "https://alasky.unistra.fr/DSS/DSSColor"],
target: "0 +0",
cooFrame: "J2000",
fov: 60,
inertia: true,
backgroundColor: "rgb(60, 60, 60)",
// Zoom toolbar
showZoomControl: false,
// Menu toolbar
showLayersControl: true,
expandLayersControl: false,
showFullscreenControl: true,
showSimbadPointerControl: false,
showCooGridControl: false,
@@ -524,14 +671,14 @@ export let Aladin = (function () {
gridOptions: {
enabled: false,
showLabels: true,
thickness: 2,
thickness: 3,
labelSize: 15
},
projection: 'SIN',
log: true,
samp: false,
realFullscreen: false,
pixelateCanvas: true
pixelateCanvas: true,
};
// realFullscreen: AL div expands not only to the size of its parent, but takes the whole available screen estate
@@ -697,18 +844,18 @@ export let Aladin = (function () {
* Sets the coordinate frame of the Aladin instance to the specified frame.
*
* @memberof Aladin
* @param {string} frameName - The name of the coordinate frame. Possible values: 'J2000', 'J2000d', 'GALACTIC'.
* @param {string} frame - The name of the coordinate frame. Possible values: 'j2000d', 'j2000', 'gal', 'icrs'. The given string is case insensitive.
*
* @example
* // Set the coordinate frame to 'J2000'
* const aladin = A.aladin('#aladin-lite-div');
* aladin.setFrame('J2000');
*/
Aladin.prototype.setFrame = function (frameName) {
if (!frameName) {
Aladin.prototype.setFrame = function (frame) {
if (!frame) {
return;
}
var newFrame = CooFrameEnum.fromString(frameName, CooFrameEnum.J2000);
var newFrame = CooFrameEnum.fromString(frame, CooFrameEnum.J2000);
if (newFrame == this.view.cooFrame) {
return;
}
@@ -843,10 +990,11 @@ export let Aladin = (function () {
// try to parse as a position
if (!isObjectName) {
var coo = new Coo();
coo.parse(targetName);
// Convert from view coo sys to icrs
const [ra, dec] = this.wasm.viewToICRSCooSys(coo.lon, coo.lat);
this.view.pointTo(ra, dec);
(typeof successCallback === 'function') && successCallback(this.getRaDec());
@@ -906,14 +1054,14 @@ export let Aladin = (function () {
* @memberof Aladin
* @param {number} lon - longitude in degrees
* @param {number} lat - latitude in degrees
* @param {string} frame - Optional callback options.
* @param {string} [frame] - The name of the coordinate frame. Possible values: 'j2000d', 'j2000', 'gal', 'icrs'. The given string is case insensitive.
*
* @example
* // Move to position
* const aladin = A.aladin('#aladin-lite-div');
* aladin.gotoPosition(20, 10, "galactic");
*/
Aladin.prototype.gotoPosition = function (lon, lat, frame = undefined) {
Aladin.prototype.gotoPosition = function (lon, lat, frame) {
var radec;
// convert the frame from string to CooFrameEnum
if (frame) {
@@ -928,14 +1076,14 @@ export let Aladin = (function () {
radec = [lon, lat];
}
this.view.pointTo(radec[0], radec[1]);
this.gotoRaDec(radec[0], radec[1]);
};
var idTimeoutAnim;
var doAnimation = function (aladin) {
if (idTimeoutAnim) {
/*if (idTimeoutAnim) {
clearTimeout(idTimeoutAnim)
}
}*/
var params = aladin.animationParams;
if (params == null || !params['running']) {
@@ -962,8 +1110,11 @@ export let Aladin = (function () {
//var curDec = params['decStart'] + (params['decEnd'] - params['decStart']) * (now-params['start']) / (params['end'] - params['start']);
aladin.gotoRaDec(curRa, curDec);
idTimeoutAnim = setTimeout(function () { doAnimation(aladin); }, 10);
//idTimeoutAnim = setTimeout(function () { doAnimation(aladin); }, 10);
requestAnimFrame(() => {
doAnimation(aladin)
})
};
/*
@@ -1129,9 +1280,15 @@ export let Aladin = (function () {
};
/**
* point to a given position, expressed as a ra,dec coordinate
* Moves the Aladin instance to the specified position given in ICRS frame
*
* @API
* @memberof Aladin
* @param {number} ra - Right-ascension in degrees
* @param {number} dec - Declination in degrees
*
* @example
* const aladin = A.aladin('#aladin-lite-div');
* aladin.gotoRaDec(20, 10);
*/
Aladin.prototype.gotoRaDec = function (ra, dec) {
this.view.pointTo(ra, dec);
@@ -1228,7 +1385,15 @@ export let Aladin = (function () {
let surveyOptions = ImageSurvey.cache[id];
if (!surveyOptions) {
surveyOptions = {url, name, maxOrder, cooFrame, ...options};
surveyOptions = {name, maxOrder, cooFrame, ...options};
// differenciate url from CDS Id in the url param given
if (!Utils.isUrl(url)) {
surveyOptions.id = url;
} else {
surveyOptions.url = url;
}
ImageSurvey.cache[id] = surveyOptions;
}
@@ -1649,19 +1814,19 @@ export let Aladin = (function () {
options.color.b /= 255;
}
this.view.setGridConfig(options);
this.view.setGridOptions(options);
}
Aladin.prototype.getGridOptions = function() {
return this.view.getGridConfig();
return this.view.getGridOptions();
}
Aladin.prototype.showCooGrid = function () {
this.view.setGridConfig({enabled: true});
this.setCooGrid({enabled: true});
};
Aladin.prototype.hideCooGrid = function() {
this.view.setGridConfig({enabled: false});
this.setCooGrid({enabled: false});
}
Aladin.prototype.layerByName = function (name) {
@@ -1937,7 +2102,7 @@ export let Aladin = (function () {
y2 = (k == 1 || k == 2) ? this.view.height - 1 : 0;
for (var step = 0; step < nbSteps; step++) {
let radec = this.wasm.screenToWorld(x1 + step / nbSteps * (x2 - x1), y1 + step / nbSteps * (y2 - y1));
let radec = this.pix2world(x1 + step / nbSteps * (x2 - x1), y1 + step / nbSteps * (y2 - y1));
points.push(radec);
}
}

View File

@@ -34,9 +34,13 @@ import { Color } from "./Color.js"
import { Utils } from "./Utils";
import { Coo } from "./libs/astro/coo.js";
import { VOTable } from "./vo/VOTable.js";
import { Footprint } from "./Footprint.js";
import { ObsCore } from "./vo/ObsCore.js";
import A from "./A.js";
import { Polyline } from "./Polyline.js";
import { Line } from "./Line.js";
import { Ellipse } from "./Ellipse.js";
import { Circle } from "./Circle.js";
import { Footprint } from "./Footprint.js";
/**
* Represents a catalog with configurable options for display and interaction.
@@ -55,7 +59,7 @@ export let Catalog = (function() {
* @param {string} [options.name="catalog"] - The name of the catalog.
* @param {string} [options.color] - The color associated with the catalog.
* @param {number} [options.sourceSize=8] - The size of the sources in the catalog.
* @param {string} [options.shape="square"] - The shape of the sources (can be, "square", "circle", "plus", "cross", "rhomb", "triangle").
* @param {string|function|Image|HTMLCanvasElement} [options.shape="square"] - The shape of the sources (can be, "square", "circle", "plus", "cross", "rhomb", "triangle").
* @param {number} [options.limit] - The maximum number of sources to display.
* @param {function} [options.onClick] - The callback function to execute on a source click.
* @param {boolean} [options.readOnly=false] - Whether the catalog is read-only.
@@ -288,14 +292,14 @@ export let Catalog = (function() {
var name = field.name || field.ID || '';
name = name.toLowerCase();
if ( ! raFieldIdx) {
if (name.indexOf('ra')==0 || name.indexOf('_ra')==0 || name.indexOf('ra(icrs)')==0 || name.indexOf('_ra')==0 || name.indexOf('alpha')==0) {
if (raFieldIdx === null) {
if (name.indexOf('ra')==0 || name.indexOf('_ra')==0 || name.indexOf('ra(icrs)')==0 || name.indexOf('alpha')==0) {
raFieldIdx = l;
continue;
}
}
if ( ! decFieldIdx) {
if (decFieldIdx === null) {
if (name.indexOf('dej2000')==0 || name.indexOf('_dej2000')==0 || name.indexOf('de')==0 || name.indexOf('de(icrs)')==0 || name.indexOf('_de')==0 || name.indexOf('delta')==0) {
decFieldIdx = l;
continue;
@@ -318,7 +322,6 @@ export let Catalog = (function() {
Catalog.parseFields = function(fields, raField, decField) {
// This votable is not an obscore one
let [raFieldIdx, decFieldIdx] = findRADecFields(fields, raField, decField);
let parsedFields = {};
let fieldIdx = 0;
fields.forEach((field) => {
@@ -361,19 +364,16 @@ export let Catalog = (function() {
}
let { fields, rows } = table;
let type;
try {
fields = ObsCore.parseFields(fields);
//fields.subtype = "ObsCore";
type = 'ObsCore';
} catch(e) {
// It is not an ObsCore table
fields = Catalog.parseFields(fields, raField, decField);
type = 'sources';
}
let sources = [];
let footprints = [];
//let footprints = [];
var coo = new Coo();
@@ -403,22 +403,23 @@ export let Catalog = (function() {
dec = coo.lat;
}
source = new Source(ra, dec, mesures);
source = new Source(parseFloat(ra), parseFloat(dec), mesures);
source.rowIdx = rowIdx;
}
let footprint = null;
if (region) {
//let footprint = null;
/*if (region) {
let shapes = A.footprintsFromSTCS(region, {lineWidth: 2})
footprint = new Footprint(shapes, source);
}
}*/
if (footprint) {
/*if (footprint) {
footprints.push(footprint);
if (maxNbSources && footprints.length == maxNbSources) {
return false;
}
} else if(source) {
} else */
if (source) {
sources.push(source);
if (maxNbSources && sources.length == maxNbSources) {
return false;
@@ -431,10 +432,9 @@ export let Catalog = (function() {
if (successCallback) {
successCallback({
sources: sources,
footprints: footprints,
fields: fields,
type: type
sources,
//footprints,
fields,
});
}
},
@@ -453,11 +453,16 @@ export let Catalog = (function() {
this.shape = options.shape || this.shape || "square";
this._shapeIsFunction = false; // if true, the shape is a function drawing on the canvas
this._shapeIsFootprintFunction = false;
if (typeof this.shape === 'function') {
this._shapeIsFunction = true;
}
// do not need to compute any canvas
if (this.shape instanceof Image || this.shape instanceof HTMLCanvasElement) {
// there is a possibility that the user gives a function returning shape objects such as
// circle, polyline, line or even footprints
// we must test that here and precompute all those objects and add them as footprints to draw
// at this point, we do not have to draw any sources
} else if (this.shape instanceof Image || this.shape instanceof HTMLCanvasElement) {
this.sourceSize = this.shape.width;
}
@@ -479,7 +484,7 @@ export let Catalog = (function() {
return;
}
if(!this.fields) {
if (!this.fields) {
// Case where we create a catalog from scratch
// We have to define its fields by looking at the source data
let fields = [];
@@ -500,20 +505,52 @@ export let Catalog = (function() {
this.dec.push(sources[k].dec);
}
this.recomputeFootprints = true;
this.reportChange();
};
Catalog.prototype.addFootprints = function(footprintsToAdd) {
/*Catalog.prototype.addFootprints = function(footprintsToAdd) {
footprintsToAdd = [].concat(footprintsToAdd); // make sure we have an array and not an individual footprints
this.footprints = this.footprints.concat(footprintsToAdd);
for (var k=0, len=footprintsToAdd.length; k<len; k++) {
footprintsToAdd[k].setCatalog(this);
footprintsToAdd[k].setColor(this.color);
footprintsToAdd[k].setSelectionColor(this.selectionColor);
footprintsToAdd[k].setHoverColor(this.hoverColor);
}
footprintsToAdd.forEach(f => {
f.setCatalog(this);
})
this.reportChange();
};
};*/
Catalog.prototype.computeFootprints = function(sources) {
let footprints = [];
if (this._shapeIsFunction) {
for(const source of sources) {
try {
let shape = this.shape(source)
// convert simple shapes to footprints
if (shape instanceof Circle || shape instanceof Polyline || shape instanceof Ellipse || shape instanceof Line) {
shape = new Footprint(shape, source);
}
if (shape instanceof Footprint) {
let footprint = shape;
this._shapeIsFootprintFunction = true;
footprint.setCatalog(this);
// store the footprints
footprints.push(footprint);
}
} catch(e) {
// do not create the footprint
continue;
}
};
}
return footprints;
}
Catalog.prototype.setFields = function(fields) {
this.fields = fields;
@@ -657,6 +694,8 @@ export let Catalog = (function() {
this.ra.splice(idx, 1);
this.dec.splice(idx, 1);
this.recomputeFootprints = true;
this.reportChange();
};
@@ -668,20 +707,21 @@ export let Catalog = (function() {
this.footprints = [];
};
Catalog.prototype.draw = function(ctx, frame, width, height, largestDim) {
if (! this.isShowing) {
Catalog.prototype.draw = function(ctx, width, height) {
if (!this.isShowing) {
return;
}
// tracé simple
ctx.strokeStyle= this.color;
ctx.strokeStyle = this.color;
// Draw the footprints first
this.drawFootprints(ctx);
//ctx.lineWidth = 1;
//ctx.beginPath();
if (this._shapeIsFunction) {
ctx.save();
}
const sourcesInView = this.drawSources(ctx, width, height);
const drawnSources = this.drawSources(ctx, width, height);
if (this._shapeIsFunction) {
ctx.restore();
@@ -691,21 +731,19 @@ export let Catalog = (function() {
if (this.displayLabel) {
ctx.fillStyle = this.labelColor;
ctx.font = this.labelFont;
sourcesInView.forEach((s) => {
drawnSources.forEach((s) => {
this.drawSourceLabel(s, ctx);
})
}
// Draw the footprints
this.drawFootprints(ctx);
};
Catalog.prototype.drawSources = function(ctx, width, height) {
let inside = [];
if (!this.sources) {
return;
}
let sourcesInsideView = [];
let xy = this.view.wasm.worldToScreenVec(this.ra, this.dec);
let self = this;
@@ -716,12 +754,12 @@ export let Catalog = (function() {
s.y = xy[2*idx + 1];
self.drawSource(s, ctx, width, height)
sourcesInsideView.push(s);
inside.push(s);
}
}
});
return sourcesInsideView;
return inside;
};
Catalog.prototype.drawSource = function(s, ctx, width, height) {
@@ -729,19 +767,23 @@ export let Catalog = (function() {
return false;
}
if (s.hasFootprint && !s.tooSmallFootprint) {
return false;
}
if (s.x <= width && s.x >= 0 && s.y <= height && s.y >= 0) {
if (this._shapeIsFunction) {
if (this._shapeIsFunction && !this._shapeIsFootprintFunction) {
this.shape(s, ctx, this.view.getViewParams());
}
else if (s.marker && s.useMarkerDefaultIcon) {
ctx.drawImage(this.cacheMarkerCanvas, s.x-this.sourceSize/2, s.y-this.sourceSize/2);
}
else if (s.isHovered) {
ctx.drawImage(this.cacheHoverCanvas, s.x-this.selectSize/2, s.y-this.selectSize/2);
}
else if (s.isSelected) {
ctx.drawImage(this.cacheSelectCanvas, s.x-this.selectSize/2, s.y-this.selectSize/2);
}
else if (s.isHovered) {
ctx.drawImage(this.cacheHoverCanvas, s.x-this.selectSize/2, s.y-this.selectSize/2);
}
else {
ctx.drawImage(this.cacheCanvas, s.x-this.cacheCanvas.width/2, s.y-this.cacheCanvas.height/2);
}
@@ -771,9 +813,20 @@ export let Catalog = (function() {
};
Catalog.prototype.drawFootprints = function(ctx) {
this.footprints.forEach((f) => {
if (this.recomputeFootprints) {
this.footprints = this.computeFootprints(this.sources);
this.recomputeFootprints = false;
}
var f;
for(let k = 0; k < this.footprints.length; k++) {
f = this.footprints[k];
f.draw(ctx, this.view)
});
f.source.tooSmallFootprint = f.isTooSmall()
// propagate the info that the footprint is too small
//f.source.tooSmallFootprint = f.isTooSmall
};
};

View File

@@ -29,13 +29,27 @@
*****************************************************************************/
import { Utils } from "./Utils";
import { AladinUtils } from "./AladinUtils.js";
import { Overlay } from "./Overlay.js";
// TODO : Circle and Footprint should inherit from the same root object
/**
* Represents an circle shape
*
* @namespace
* @typedef {Object} Circle
*/
export let Circle = (function() {
// constructor
let Circle = function(centerRaDec, radiusDegrees, options) {
/**
* Constructor function for creating a new circle.
*
* @constructor
* @memberof Circle
* @param {number[]} center - 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) {
options = options || {};
this.color = options['color'] || undefined;
@@ -47,8 +61,8 @@ export let Circle = (function() {
// TODO : all graphic overlays should have an id
this.id = 'circle-' + Utils.uuidv4();
this.setCenter(centerRaDec);
this.setRadius(radiusDegrees);
this.setCenter(center);
this.setRadius(radius);
this.overlay = null;
this.isShowing = true;
@@ -57,7 +71,7 @@ export let Circle = (function() {
};
Circle.prototype.setColor = function(color) {
if (this.color == color) {
if (!color || this.color == color) {
return;
}
this.color = color;
@@ -67,7 +81,7 @@ export let Circle = (function() {
};
Circle.prototype.setSelectionColor = function(color) {
if (this.selectionColor == color) {
if (!color || this.selectionColor == color) {
return;
}
this.selectionColor = color;
@@ -77,7 +91,7 @@ export let Circle = (function() {
};
Circle.prototype.setHoverColor = function(color) {
if (this.hoverColor == color) {
if (!color || this.hoverColor == color) {
return;
}
this.hoverColor = color;
@@ -185,15 +199,15 @@ export let Circle = (function() {
// TODO
Circle.prototype.draw = function(ctx, view, noStroke) {
if (! this.isShowing) {
return;
return false;
}
noStroke = noStroke===true || false;
var centerXyview = AladinUtils.radecToViewXy(this.centerRaDec[0], this.centerRaDec[1], view.aladin);
var centerXyview = view.aladin.world2pix(this.centerRaDec[0], this.centerRaDec[1]);
if (!centerXyview) {
// the center goes out of the projection
// we do not draw it
return;
return false;
}
this.center = {
x: centerXyview[0],
@@ -203,42 +217,27 @@ export let Circle = (function() {
let hidden = true;
var ra, dec, vertOnCircle, dx, dy;
//if (this.radiusDegrees > 30) {
this.radius = Number.NEGATIVE_INFINITY;
// Project 4 points lying on the circle and take the minimal dist with the center as radius
[[-1, 0], [1, 0], [0, -1], [0, 1]].forEach(([cardDirRa, cardDirDec]) => {
ra = this.centerRaDec[0] + cardDirRa * this.radiusDegrees;
dec = this.centerRaDec[1] + cardDirDec * this.radiusDegrees;
this.radius = Number.NEGATIVE_INFINITY;
// Project 4 points lying on the circle and take the minimal dist with the center as radius
[[-1, 0], [1, 0], [0, -1], [0, 1]].forEach(([cardDirRa, cardDirDec]) => {
ra = this.centerRaDec[0] + cardDirRa * this.radiusDegrees;
dec = this.centerRaDec[1] + cardDirDec * this.radiusDegrees;
vertOnCircle = AladinUtils.radecToViewXy(ra, dec, view.aladin);
if (vertOnCircle) {
dx = vertOnCircle[0] - this.center.x;
dy = vertOnCircle[1] - this.center.y;
this.radius = Math.max(Math.sqrt(dx*dx + dy*dy), this.radius);
hidden = false;
}
});
/*} else {
ra = this.centerRaDec[0] + this.radiusDegrees;
dec = this.centerRaDec[1];
vertOnCircle = AladinUtils.radecToViewXy(ra, dec, view);
vertOnCircle = view.aladin.world2pix(ra, dec);
if (vertOnCircle) {
dx = vertOnCircle[0] - this.center.x;
dy = vertOnCircle[1] - this.center.y;
this.radius = Math.sqrt(dx*dx + dy*dy);
this.radius = Math.max(Math.sqrt(dx*dx + dy*dy), this.radius);
hidden = false;
}
}*/
}
});
if (hidden) {
return;
return false;
}
// Then we can draw
@@ -272,6 +271,8 @@ export let Circle = (function() {
}
ctx.stroke();
}
return true;
};
Circle.prototype.isInStroke = function(ctx, view, x, y) {

View File

@@ -49,7 +49,12 @@ export let DefaultActionsForContextMenu = (function () {
{
label: "Copy position", action(o) {
let msg;
navigator.clipboard.writeText(o.target.innerText)
let text = o.target.innerText;
if (!text) {
return false;
}
navigator.clipboard.writeText(text)
.then(() => {
msg = 'successful'
if (aladinInstance.statusBar) {

View File

@@ -29,13 +29,44 @@
*****************************************************************************/
import { Utils } from "./Utils";
import { AladinUtils } from "./AladinUtils.js";
import { Overlay } from "./Overlay.js";
import { requestAnimFrame } from "./libs/RequestAnimationFrame";
// TODO : Ellipse, Circle and Footprint should inherit from the same root object
/**
* @typedef {Object} ShapeOptions
* @description Options for describing a shape
*
* @property {Object} options - Configuration options for the shape.
* @property {string} [options.color] - The color of the shape
* @property {string} [options.fill=false] - Fill the shape with fillColor
* @property {string} [options.fillColor] - A filling color for the shape
* @property {number} [options.lineWidth=2] - The line width in pixels
* @property {number} [options.opacity=1] - The opacity, between 0 (totally transparent) and 1 (totally opaque)
* @property {string} [options.selectionColor='#00ff00'] - A selection color
* @property {string} [options.hoverColor] - A hovered color
*/
/**
* Represents an ellipse shape
*
* @namespace
* @typedef {Object} Ellipse
*/
export let Ellipse = (function() {
// constructor
let Ellipse = function(centerRaDec, rayonXDegrees, rayonYDegrees, rotationDegrees, options) {
/**
* Constructor function for creating a new ellipse.
*
* @constructor
* @memberof Ellipse
* @param {number[]} center - 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
* @param {ShapeOptions} options - Configuration options for the ellipse
*
* @returns {Ellipse} - The ellipse shape object
*/
let Ellipse = function(center, a, b, theta, options) {
options = options || {};
this.color = options['color'] || undefined;
@@ -43,13 +74,14 @@ export let Ellipse = (function() {
this.lineWidth = options["lineWidth"] || 2;
this.selectionColor = options["selectionColor"] || '#00ff00';
this.hoverColor = options["hoverColor"] || undefined;
this.opacity = options['opacity'] || 1;
// TODO : all graphic overlays should have an id
this.id = 'ellipse-' + Utils.uuidv4();
this.setCenter(centerRaDec);
this.setRadiuses(rayonXDegrees, rayonYDegrees);
this.setRotation(rotationDegrees);
this.setCenter(center);
this.setAxisLength(a, b);
this.setRotation(theta);
this.overlay = null;
this.isShowing = true;
@@ -58,7 +90,7 @@ export let Ellipse = (function() {
};
Ellipse.prototype.setColor = function(color) {
if (this.color == color) {
if (!color || this.color == color) {
return;
}
this.color = color;
@@ -68,7 +100,7 @@ export let Ellipse = (function() {
};
Ellipse.prototype.setSelectionColor = function(color) {
if (this.selectionColor == color) {
if (!color || this.selectionColor == color) {
return;
}
this.selectionColor = color;
@@ -78,7 +110,7 @@ export let Ellipse = (function() {
};
Ellipse.prototype.setHoverColor = function(color) {
if (this.hoverColor == color) {
if (!color || this.hoverColor == color) {
return;
}
this.hoverColor = color;
@@ -186,9 +218,9 @@ export let Ellipse = (function() {
}
};
Ellipse.prototype.setRadiuses = function(radiusXDegrees, radiusYDegrees) {
this.radiusXDegrees = radiusXDegrees;
this.radiusYDegrees = radiusYDegrees;
Ellipse.prototype.setAxisLength = function(a, b) {
this.a = a;
this.b = b;
if (this.overlay) {
this.overlay.reportChange();
@@ -200,20 +232,58 @@ export let Ellipse = (function() {
}
// TODO
Ellipse.prototype.draw = function(ctx, view, noStroke) {
Ellipse.prototype.draw = function(ctx, view, noStroke, noSmallCheck) {
if (! this.isShowing) {
return;
return false;
}
var centerXyview = AladinUtils.radecToViewXy(this.centerRaDec[0], this.centerRaDec[1], view.aladin);
if (!centerXyview) {
const px_per_deg = view.width / view.fov;
noSmallCheck = noSmallCheck===true || false;
if (!noSmallCheck) {
this.isTooSmall = this.b * 2 * px_per_deg < this.lineWidth;
if (this.isTooSmall) {
return false;
}
}
var originScreen = view.aladin.world2pix(this.centerRaDec[0], this.centerRaDec[1]);
if (!originScreen) {
// the center goes out of the projection
// we do not draw it
return;
return false;
}
let circlePtXyViewRa = AladinUtils.radecToViewXy(this.centerRaDec[0] + this.radiusXDegrees, this.centerRaDec[1], view.aladin);
let circlePtXyViewDec = AladinUtils.radecToViewXy(this.centerRaDec[0], this.centerRaDec[1] + this.radiusYDegrees, view.aladin);
// 1. Find the spherical tangent vector going to the north
let toNorth = [this.centerRaDec[0], this.centerRaDec[1] + 1e-3];
// 2. Project it to the screen
let toNorthScreen = view.aladin.world2pix(toNorth[0], toNorth[1]);
if(!toNorthScreen) {
return false;
}
// 3. normalize this vector
let toNorthVec = [toNorthScreen[0] - originScreen[0], toNorthScreen[1] - originScreen[1]];
let norm = Math.sqrt(toNorthVec[0]*toNorthVec[0] + toNorthVec[1]*toNorthVec[1]);
toNorthVec = [toNorthVec[0] / norm, toNorthVec[1] / norm];
let toWestVec = [1.0, 0.0];
let x1 = toWestVec[0];
let y1 = toWestVec[1];
let x2 = toNorthVec[0];
let y2 = toNorthVec[1];
// 4. Compute the west to north angle
let westToNorthAngle = Math.atan2(x1*y2-y1*x2, x1*x2+y1*y2);
// 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
@@ -223,17 +293,20 @@ export let Ellipse = (function() {
var dxRa = circlePtXyViewRa[0] - centerXyview[0];
var dyRa = circlePtXyViewRa[1] - centerXyview[1];
var radiusInPixX = Math.sqrt(dxRa*dxRa + dyRa*dyRa);
var dRa = Math.sqrt(dxRa*dxRa + dyRa*dyRa);
var dxDec = circlePtXyViewDec[0] - centerXyview[0];
var dyDec = circlePtXyViewDec[1] - centerXyview[1];
var radiusInPixY = Math.sqrt(dxDec*dxDec + dyDec*dyDec);
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) {
/*if ((dxRa*dyDec - dxDec*dyRa) <= 0.0) {
// We do not draw it
return;
}
}*/
noStroke = noStroke===true || false;
// TODO : check each 4 point until show
@@ -257,33 +330,11 @@ export let Ellipse = (function() {
ctx.strokeStyle = baseColor;
}
// 1. Find the spherical tangent vector going to the north
let origin = this.centerRaDec;
let toNorth = [this.centerRaDec[0], this.centerRaDec[1] + 1e-3];
// 2. Project it to the screen
let originScreen = this.overlay.view.wasm.worldToScreen(origin[0], origin[1]);
let toNorthScreen = this.overlay.view.wasm.worldToScreen(toNorth[0], toNorth[1]);
// 3. normalize this vector
let toNorthVec = [toNorthScreen[0] - originScreen[0], toNorthScreen[1] - originScreen[1]];
let norm = Math.sqrt(toNorthVec[0]*toNorthVec[0] + toNorthVec[1]*toNorthVec[1]);
toNorthVec = [toNorthVec[0] / norm, toNorthVec[1] / norm];
let toWestVec = [1.0, 0.0];
let x1 = toWestVec[0];
let y1 = toWestVec[1];
let x2 = toNorthVec[0];
let y2 = toNorthVec[1];
// 4. Compute the west to north angle
let westToNorthAngle = Math.atan2(x1*y2-y1*x2, x1*x2+y1*y2);
// 5. Get the correct ellipse angle
let theta = -this.rotation + westToNorthAngle;
ctx.lineWidth = this.lineWidth;
ctx.globalAlpha = this.opacity;
ctx.beginPath();
ctx.ellipse(centerXyview[0], centerXyview[1], radiusInPixX, radiusInPixY, theta, 0, 2*Math.PI, false);
ctx.ellipse(originScreen[0], originScreen[1], px_per_deg * this.a, px_per_deg * this.b, theta, 0, 2*Math.PI, false);
if (!noStroke) {
if (this.fillColor) {
ctx.fillStyle = this.fillColor;
@@ -291,10 +342,15 @@ export let Ellipse = (function() {
}
ctx.stroke();
}
return true;
};
Ellipse.prototype.isInStroke = function(ctx, view, x, y) {
this.draw(ctx, view, true);
if (!this.draw(ctx, view, true, true)) {
return false;
}
return ctx.isPointInStroke(x, y);
};

View File

@@ -56,10 +56,6 @@ export class CircleSelect extends FSM {
let draw = () => {
let ctx = view.catalogCtx;
if (!view.catalogCanvasCleared) {
ctx.clearRect(0, 0, view.width, view.height);
view.catalogCanvasCleared = true;
}
// draw the selection
ctx.fillStyle = options.color + '7f';
ctx.strokeStyle = options.color;

View File

@@ -119,10 +119,6 @@ export class PolySelect extends FSM {
let draw = () => {
let ctx = view.catalogCtx;
if (!view.catalogCanvasCleared) {
ctx.clearRect(0, 0, view.width, view.height);
view.catalogCanvasCleared = true;
}
// draw the selection
ctx.save();
ctx.fillStyle = options.color + '7f';

View File

@@ -57,10 +57,6 @@ export class RectSelect extends FSM {
let draw = () => {
let ctx = view.catalogCtx;
if (!view.catalogCanvasCleared) {
ctx.clearRect(0, 0, view.width, view.height);
view.catalogCanvasCleared = true;
}
// draw the selection
ctx.fillStyle = options.color + '7f';
ctx.strokeStyle = options.color;

View File

@@ -33,7 +33,6 @@
*
*****************************************************************************/
import { AladinUtils } from './AladinUtils.js';
import { Utils } from './Utils';
export let Footprint= (function() {
@@ -43,7 +42,11 @@ export let Footprint= (function() {
this.id = 'footprint-' + Utils.uuidv4();
this.source = source;
this.shapes = shapes;
if (this.source) {
this.source.hasFootprint = true;
}
this.shapes = [].concat(shapes);
this.isShowing = true;
this.isHovered = false;
@@ -54,6 +57,16 @@ export let Footprint= (function() {
Footprint.prototype.setCatalog = function(catalog) {
if (this.source) {
this.source.setCatalog(catalog);
for (var s of this.shapes) {
if (!s.color) {
s.setColor(catalog.color);
}
// Selection and hover color are catalog options
s.setSelectionColor(catalog.selectionColor);
s.setHoverColor(catalog.hoverColor);
}
}
};
@@ -93,8 +106,12 @@ export let Footprint= (function() {
if (this.overlay) {
this.overlay.reportChange();
} else if (this.source && this.source.catalog) {
this.source.catalog.view && this.source.catalog.view.requestRedraw();
return;
}
let catalog = this.getCatalog();
if (catalog) {
catalog.view && catalog.view.requestRedraw();
}
};
@@ -108,30 +125,43 @@ export let Footprint= (function() {
if (this.overlay) {
this.overlay.reportChange();
} else if (this.source && this.source.catalog) {
this.source.catalog.view && this.source.catalog.view.requestRedraw();
}
let catalog = this.getCatalog();
if (catalog) {
catalog.view && catalog.view.requestRedraw();
}
};
Footprint.prototype.getLineWidth = function() {
return this.shapes && this.shapes[0].getLineWidth();
};
Footprint.prototype.setLineWidth = function(lineWidth) {
this.shapes.forEach((shape) => shape.setLineWidth(lineWidth))
};
Footprint.prototype.getLineWidth = function() {
if (this.shapes && this.shapes.length > 0) {
return this.shapes[0].getLineWidth();
}
};
Footprint.prototype.setColor = function(color) {
if(!color) {
return;
}
this.shapes.forEach((shape) => shape.setColor(color))
};
Footprint.prototype.setSelectionColor = function(color) {
if (!color) {
return;
}
this.shapes.forEach((shape) => shape.setSelectionColor(color))
};
Footprint.prototype.setHoverColor = function(color) {
if (!color)
return;
this.shapes.forEach((shape) => shape.setHoverColor(color))
};
@@ -140,7 +170,7 @@ export let Footprint= (function() {
}
Footprint.prototype.draw = function(ctx, view, noStroke) {
this.shapes.forEach((shape) => shape.draw(ctx, view, noStroke))
return this.shapes.some((shape) => {return shape.draw(ctx, view, noStroke)})
};
Footprint.prototype.actionClicked = function() {
@@ -162,6 +192,10 @@ export let Footprint= (function() {
return this.shapes.some((shape) => shape.isInStroke(ctx, view, x, y));
};
Footprint.prototype.isTooSmall = function(view) {
return this.shapes.every((shape) => shape.isTooSmall);
};
Footprint.prototype.getCatalog = function() {
return this.source && this.source.catalog;
};
@@ -185,7 +219,7 @@ export let Footprint= (function() {
y: s.y,
};
} else {
var xy = AladinUtils.radecToViewXy(s.ra, s.dec, view.aladin);
var xy = view.aladin.world2pix(s.ra, s.dec);
if (!xy) {
return false;
}

View File

@@ -214,14 +214,15 @@ HiPSProperties.getFasterMirrorUrl = function (metadata, currUrl) {
newUrlResp = validResponses[0];
} else {
// no valid response => we return an error
return Promise.reject('Survey not found. All mirrors urls have been tested:' + urls)
return Promise.reject('All mirrors urls have been tested:' + urls)
}
// check if there is a big difference from the current one
let currUrlResp = validResponses.find((r) => r.baseUrl === currUrl)
// it may happen that the url requested by the user is too slow hence discarded
// for these cases, we automatically switch to the new fastest url.
let urlChosen;
if (Math.abs(currUrlResp.duration - newUrlResp.duration) / Math.min(currUrlResp.duration, newUrlResp.duration) < 0.10) {
if (currUrlResp && Math.abs(currUrlResp.duration - newUrlResp.duration) / Math.min(currUrlResp.duration, newUrlResp.duration) < 0.10) {
// there is not enough difference => do not switch
urlChosen = currUrlResp.baseUrl;
} else {

View File

@@ -144,8 +144,7 @@ export let ImageSurvey = (function () {
* @class
* @constructs ImageSurvey
*
* @param {string} id - Mandatory unique identifier for the layer.
* Can be an arbitrary name
* @param {string} id - Mandatory unique identifier for the layer. Can be an arbitrary name
* @param {string} url - Can be an url to the survey or a "CDS" ID pointing towards a HiPS. One can found the list of IDs {@link https://aladin.cds.unistra.fr/hips/list| here}
* @param {ImageSurveyOptions} [options] - The option for the survey
*
@@ -193,32 +192,29 @@ export let ImageSurvey = (function () {
self.query = (async () => {
if (isMOCServerToBeQueried) {
let properties;
let isCDSId = false;
try {
properties = await HiPSProperties.fetchFromUrl(self.url)
/*.catch((e) => {
// try with the proxy
url = Utils.handleCORSNotSameOrigin(url).href;
return HiPSProperties.fetchFromUrl(url);
})*/
.catch(async (e) => {
// url not valid so we try with the id
try {
isCDSId = true;
// the url stores a "CDS ID" we take it prioritaly
// if the url is null, take the id, this is for some tests
// to pass because some users might just give null as url param and a "CDS ID" as id param
let id = self.url || self.id;
return await HiPSProperties.fetchFromID(id);
} catch(e) {
throw e;
}
})
} catch(e) {
throw e;
}
let properties = await HiPSProperties.fetchFromUrl(self.url)
/*.catch((e) => {
// try with the proxy
url = Utils.handleCORSNotSameOrigin(url).href;
return HiPSProperties.fetchFromUrl(url);
})*/
.catch(async (e) => {
// url not valid so we try with the id
try {
isCDSId = true;
// the url stores a "CDS ID" we take it prioritaly
// if the url is null, take the id, this is for some tests
// to pass because some users might just give null as url param and a "CDS ID" as id param
let id = self.url || self.id;
return await HiPSProperties.fetchFromID(id);
} catch(e) {
throw e;
}
})
//obsTitle = properties.obs_title;
self.creatorDid = properties.creator_did || self.creatorDid;
@@ -238,24 +234,25 @@ export let ImageSurvey = (function () {
if (self.url !== url) {
console.info("Change url of ", self.id, " from ", self.url, " to ", url)
self.url = url;
// save the new url to the cache
self._saveInCache();
// If added to the backend, then we need to tell it the url has changed
if (self.added) {
self.view.wasm.setHiPSUrl(self.creatorDid, url);
}
self.url = url;
// save the new url to the cache
ImageSurvey.cache[self.id].url = self.url;
}
})
/*.catch(e => {
.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)
//self.view.removeImageLayer(self.layer)
//throw e;
})*/
})
}
// Max order
@@ -355,6 +352,7 @@ export let ImageSurvey = (function () {
minCut = self.colorCfg.minCut || 0.0;
maxCut = self.colorCfg.maxCut || 1.0;
}
self.colorCfg.setCuts(minCut, maxCut);
// Coo frame
@@ -369,15 +367,16 @@ export let ImageSurvey = (function () {
self.formats = self.formats || [self.imgFormat];
self._save();
self._saveInCache();
return self;
})();
}
ImageSurvey.prototype._save = function() {
ImageSurvey.prototype._saveInCache = function() {
let self = this;
let colorOpt = Object.fromEntries(Object.entries(this.colorCfg));
let surveyOpt = {
creatorDid: self.creatorDid,
name: self.name,
@@ -386,6 +385,7 @@ export let ImageSurvey = (function () {
maxOrder: self.maxOrder,
tileSize: self.tileSize,
imgFormat: self.imgFormat,
...colorOpt
}
if (self.numBitsPerPixel) {
@@ -399,7 +399,13 @@ export let ImageSurvey = (function () {
// append new important infos from the properties queried
...surveyOpt,
}
}
//console.log('new CACHE', ImageSurvey.cache, self.id, surveyOpt, ImageSurvey.cache[self.id], ImageSurvey.cache["CSIRO/P/RACS/mid/I"])
// Tell that the HiPS List has been updated
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(this.view.aladin.aladinDiv);
}
/**
* Checks if the ImageSurvey represents a planetary body.
@@ -469,6 +475,7 @@ export let ImageSurvey = (function () {
// Switch from png/webp/jpeg to fits
if ((self.imgFormat === 'png' || self.imgFormat === "webp" || self.imgFormat === "jpeg") && imgFormat === 'fits') {
if (self.minCut && self.maxCut) {
// reset cuts to those given from the properties
self.setCuts(self.minCut, self.maxCut)
}
// Switch from fits to png/webp/jpeg
@@ -662,6 +669,9 @@ export let ImageSurvey = (function () {
// once the meta have been well parsed, we can set the meta
ALEvent.HIPS_LAYER_CHANGED.dispatchedTo(this.view.aladinDiv, { layer: this });
}
// save it in the JS HiPS cache
this._saveInCache()
} catch (e) {
// Display the error message
console.error(e);
@@ -697,7 +707,7 @@ export let ImageSurvey = (function () {
},
});
this.added = true;
//this.added = true;
return Promise.resolve(this);
}
@@ -741,7 +751,7 @@ export let ImageSurvey = (function () {
// A cache storing directly surveys important information to not query for the properties each time
ImageSurvey.cache = {
DSS2_color: {
/*DSS2_color: {
creatorDid: "ivo://CDS/P/DSS2/color",
name: "DSS colored",
url: "https://alasky.cds.unistra.fr/DSS/DSSColor",
@@ -891,7 +901,7 @@ export let ImageSurvey = (function () {
imgFormat: 'jpeg',
cooFrame: 'equatorial'
},
/*SDSS9_g: {
SDSS9_g: {
creatorDid: "ivo://CDS/P/SDSS9/g",
id: "P/SDSS9/g",
name: "SDSS9 band-g",
@@ -905,7 +915,8 @@ export let ImageSurvey = (function () {
maxCut: 1.8,
stretch: 'linear',
colormap: "redtemperature",
}*/
}
*/
/*
{
id: "P/Finkbeiner",

View File

@@ -29,77 +29,161 @@
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
import { Polyline } from "./Polyline.js";
import { Utils } from './Utils';
import { Overlay } from "./Overlay.js";
import { Ellipse } from "./Ellipse.js";
/**
* Represents an line shape
*
* @namespace
* @typedef {Object} Line
*/
export let Line = (function() {
// constructor
let Line = function(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
/**
* Constructor function for creating a new line.
*
* @constructor
* @memberof Line
* @param {number} ra1 - Right Ascension (RA) coordinate of the center in degrees.
* @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) {
options = options || {};
this.color = options['color'] || undefined;
this.opacity = options['opacity'] || undefined;
this.lineWidth = options["lineWidth"] || undefined;
this.selectionColor = options["selectionColor"] || '#00ff00';
this.hoverColor = options["hoverColor"] || undefined;
this.arrow = options["arrow"] === undefined ? false : options["arrow"];
// All graphics overlay have an id
this.id = 'line-' + Utils.uuidv4();
this.overlay = null;
this.isShowing = true;
this.isSelected = false;
this.isHovered = false;
this.ra1 = ra1;
this.dec1 = dec1;
this.ra2 = ra2;
this.dec2 = dec2;
this.frame = frame;
};
// Method for testing whether a line is inside the view
// http://www.jeffreythompson.org/collision-detection/line-rect.php
Line.prototype.isInsideView = function(rw, rh) {
if (this.x1 >= 0 && this.x1 <= rw && this.y1 >= 0 && this.y1 <= rh) {
Line.prototype = {
setOverlay: Polyline.prototype.setOverlay,
isFootprint: Polyline.prototype.isFootprint,
show: Polyline.prototype.show,
hide: Polyline.prototype.hide,
select: Polyline.prototype.select,
deselect: Polyline.prototype.deselect,
hover: Polyline.prototype.hover,
unhover: Polyline.prototype.unhover,
getLineWidth: Polyline.prototype.getLineWidth,
setLineWidth: Polyline.prototype.setLineWidth,
setColor: Polyline.prototype.setColor,
setSelectionColor: Polyline.prototype.setSelectionColor,
setHoverColor: Polyline.prototype.setHoverColor,
draw: function(ctx, view, noStroke, noSmallCheck) {
noStroke = noStroke===true || false;
noSmallCheck = noSmallCheck===true || false;
// project
const v1 = view.aladin.world2pix(this.ra1, this.dec1, this.frame);
if (!v1)
return false;
const v2 = view.aladin.world2pix(this.ra2, this.dec2, this.frame);
if (!v2)
return false;
const xmin = Math.min(v1[0], v2[0]);
const xmax = Math.max(v1[0], v2[0]);
const ymin = Math.min(v1[1], v2[1]);
const ymax = Math.max(v1[1], v2[1]);
// out of bbox
if (xmax < 0 || xmin > view.width || ymax < 0 || ymin > view.height) {
return false;
}
let baseColor = this.color || (this.overlay && this.overlay.color) || '#ff0000';
let lineWidth = this.lineWidth || this.overlay.lineWidth || 3;
// too small
if(!noSmallCheck) {
this.isTooSmall = (xmax - xmin) < 1 && (ymax - ymin) < 1;
if (this.isTooSmall) {
return false;
}
}
if (this.isSelected) {
ctx.strokeStyle = this.selectionColor || Overlay.increaseBrightness(baseColor, 50);
} else if (this.isHovered) {
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
} else {
ctx.strokeStyle = baseColor;
}
ctx.lineWidth = lineWidth;
ctx.globalAlpha = this.opacity;
ctx.beginPath();
ctx.moveTo(v1[0], v1[1]);
ctx.lineTo(v2[0], v2[1]);
if (this.arrow) {
// draw the arrow
var angle, x, y, xh, yh;
var arrowRad = this.lineWidth * 3;
angle = Math.atan2(v2[1] - v1[1], v2[0] - v1[0])
xh = v2[0];
yh = v2[1];
//ctx.moveTo(xh, yh);
var t = angle + Math.PI * 3 / 4;
x = arrowRad * Math.cos(t) + v2[0];
y = arrowRad * Math.sin(t) + v2[1];
ctx.moveTo(x, y);
ctx.lineTo(xh, yh);
var t = angle - Math.PI * 3 / 4;
x = arrowRad *Math.cos(t) + v2[0];
y = arrowRad *Math.sin(t) + v2[1];
ctx.lineTo(x, y);
}
if (!noStroke) {
ctx.stroke();
}
return true;
}
if (this.x2 >= 0 && this.x2 <= rw && this.y2 >= 0 && this.y2 <= rh) {
return true;
}
},
// check if the line has hit any of the rectangle's sides
// uses the Line/Line function below
let left = Line.intersectLine(this.x1, this.y1, this.x2, this.y2, 0, 0, 0, rh);
let right = Line.intersectLine(this.x1, this.y1, this.x2, this.y2, rw, 0, rw, rh);
let top = Line.intersectLine(this.x1, this.y1, this.x2, this.y2, 0, 0, rw, 0);
let bottom = Line.intersectLine(this.x1, this.y1, this.x2, this.y2, 0, rh, rw, rh);
// if ANY of the above are true, the line
// has hit the rectangle
if (left || right || top || bottom) {
return true;
}
isInStroke: Ellipse.prototype.isInStroke,
return false;
};
Line.prototype.isFootprint = function() {
return false;
}
Line.prototype.draw = function(ctx, noStroke) {
noStroke = noStroke===true || false;
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
if (!noStroke) {
ctx.stroke();
}
};
Line.intersectLine = function(x1, y1, x2, y2, x3, y3, x4, y4) {
// Calculate the direction of the lines
let uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
let uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
// If uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
return true;
}
return false;
};
Line.prototype.isInStroke = function(ctx, view, x, y) {
this.draw(ctx, view, true);
return ctx.isPointInStroke(x, y);
};
Line.prototype.intersectsBBox = function(x, y, w, h) {
// todo
/*Line.prototype.intersectsBBox = function(x, y, w, h) {
// todo
};*/
};
return Line;

View File

@@ -15,7 +15,35 @@ import { Color } from "./Color.js";
import { ALEvent } from "./events/ALEvent.js";
/**
* @typedef {Object} MOCOptions
* @description Options for configuring a MOC (Multi-Order-Coverage).
*
* @property {Object} options - Configuration options for the MOC.
* @property {string} [options.name="MOC"] - The name of the catalog.
* @property {string} [options.color] - The color of the MOC HEALPix cell edges.
* @property {string} [options.fillColor] - A filling color of the MOC HEALPix cells.
* @property {string} [options.fill=false] - Fill the MOC with `options.fillColor`
* @property {string} [options.edge=true] - Draw the edges of the HEALPix cells with `options.color`.
* @property {number} [options.lineWidth=3] - The line width in pixels
* @property {Boolean} [options.perimeter=false] - A filling color of the MOC HEALPix cells.
* @property {number} [options.opacity=1.0] - The opacity of the MOC
*/
/**
* Represents a Multi-Order-Coverage with configurable options for display and interaction.
*
* @namespace
* @typedef {Object} MOC
*/
export let MOC = (function() {
/**
* Constructor function for creating a new catalog instance.
*
* @constructor
* @memberof MOC
* @param {MOCOptions} options - Configuration options for the MOC.
*/
let MOC = function(options) {
//this.order = undefined;
@@ -61,7 +89,7 @@ export let MOC = (function() {
}
this.opacity = Math.max(0, Math.min(1, this.opacity)); // 0 <= this.opacity <= 1
this.lineWidth = options["lineWidth"] || 1;
this.lineWidth = options["lineWidth"] || 3;
//this.proxyCalled = false; // this is a flag to check whether we already tried to load the MOC through the proxy
@@ -82,7 +110,7 @@ export let MOC = (function() {
* set MOC data by parsing a MOC serialized in JSON
* (as defined in IVOA MOC document, section 3.1.1)
*/
MOC.prototype.parse = function(data, successCallback) {
MOC.prototype.parse = function(data, successCallback, errorCallback) {
if (typeof data === 'string' || data instanceof String) {
let url = data;
this.promiseFetchData = fetch(url)
@@ -92,7 +120,7 @@ export let MOC = (function() {
}
this.successCallback = successCallback;
this.errorCallback = this.errorCallback;
this.errorCallback = errorCallback;
};
MOC.prototype.setView = function(view) {
@@ -138,7 +166,11 @@ export let MOC = (function() {
self.view.requestRedraw();
})
.catch(e => alert('MOC load error:' + e))
.catch(e => {
console.error('MOC load error:' + e)
if (self.errorCallback)
self.errorCallback(self);
})
};
MOC.prototype.reportChange = function() {

View File

@@ -33,24 +33,41 @@
*
*****************************************************************************/
import { AladinUtils } from './AladinUtils.js';
import { Line } from './Line.js';
import { Utils } from './Utils';
import { Overlay } from "./Overlay.js";
import { ProjectionEnum } from "./ProjectionEnum.js";
export let Polyline= (function() {
function _calculateMag2ForNoSinProjections(line, view) {
/**
* Represents a polyline shape
*
* @namespace
* @typedef {Object} Polyline
*/
export let Polyline = (function() {
function _calculateMag2ForNoSinProjections(l, view) {
// check if the line is too big (in the clip space) to be drawn
const [x1, y1] = view.wasm.screenToClip(line.x1, line.y1);
const [x2, y2] = view.wasm.screenToClip(line.x2, line.y2);
const [x1, y1] = view.wasm.screenToClip(l.x1, l.y1);
const [x2, y2] = view.wasm.screenToClip(l.x2, l.y2);
const mag2 = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2);
return mag2;
}
function _isAcrossCollignonZoneForHpxProjection(line, view) {
function _drawLine(l, ctx, noStroke) {
noStroke = noStroke===true || false;
ctx.beginPath();
ctx.moveTo(l.x1, l.y1);
ctx.lineTo(l.x2, l.y2);
if (!noStroke) {
ctx.stroke();
}
}
/*function _isAcrossCollignonZoneForHpxProjection(line, view) {
const [x1, y1] = view.wasm.screenToClip(line.x1, line.y1);
const [x2, y2] = view.wasm.screenToClip(line.x2, line.y2);
@@ -73,9 +90,19 @@ export let Polyline= (function() {
}
return false;
}
}*/
// constructor
/**
* Constructor function for creating a new polyline.
*
* @constructor
* @memberof Polyline
* @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) {
options = options || {};
this.color = options['color'] || undefined;
@@ -86,11 +113,7 @@ export let Polyline= (function() {
this.selectionColor = options["selectionColor"] || '#00ff00';
this.hoverColor = options["hoverColor"] || undefined;
if (options["closed"]) {
this.closed = options["closed"];
} else {
this.closed = false;
}
this.closed = (options["closed"] !== undefined) ? options["closed"] : false;
// All graphics overlay have an id
this.id = 'polyline-' + Utils.uuidv4();
@@ -183,9 +206,10 @@ export let Polyline= (function() {
};
Polyline.prototype.setColor = function(color) {
if (this.color == color) {
if (!color || this.color == color) {
return;
}
this.color = color;
if (this.overlay) {
this.overlay.reportChange();
@@ -193,7 +217,7 @@ export let Polyline= (function() {
};
Polyline.prototype.setSelectionColor = function(color) {
if (this.selectionColor == color) {
if (!color || this.selectionColor == color) {
return;
}
this.selectionColor = color;
@@ -203,7 +227,7 @@ export let Polyline= (function() {
};
Polyline.prototype.setHoverColor = function(color) {
if (this.hoverColor == color) {
if (!color || this.hoverColor == color) {
return;
}
this.hoverColor = color;
@@ -219,11 +243,11 @@ export let Polyline= (function() {
Polyline.prototype.draw = function(ctx, view, noStroke) {
if (! this.isShowing) {
return;
return false;
}
if (! this.radecArray || this.radecArray.length<2) {
return;
return false;
}
noStroke = noStroke===true || false;
@@ -237,7 +261,7 @@ export let Polyline= (function() {
}
if (!this.lineWidth) {
this.lineWidth = this.overlay.lineWidth || 2;
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
}
if (this.isSelected) {
@@ -263,9 +287,9 @@ export let Polyline= (function() {
let ymax = Number.NEGATIVE_INFINITY;
for (var k=0; k<len; k++) {
var xyview = AladinUtils.radecToViewXy(this.radecArray[k][0], this.radecArray[k][1], view.aladin);
var xyview = view.aladin.world2pix(this.radecArray[k][0], this.radecArray[k][1]);
if (!xyview) {
return;
return false;
}
xyView.push({x: xyview[0], y: xyview[1]});
@@ -276,9 +300,13 @@ export let Polyline= (function() {
ymax = Math.max(ymax, xyview[1]);
}
// 2. do not draw the polygon if it lies in less than 1 pixel
if ((xmax - xmin) < 1 || (ymax - ymin) < 1) {
return;
// 2. do not draw the polygon if it lies in less than linewidth pixels
if (xmax < 0 || xmin > view.width || ymax < 0 || ymin > view.height) {
return false;
}
if ((xmax - xmin) < this.lineWidth || (ymax - ymin) < this.lineWidth) {
return false;
}
let drawLine;
@@ -286,27 +314,28 @@ export let Polyline= (function() {
if (view.projection === ProjectionEnum.SIN) {
drawLine = (v0, v1) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (line.isInsideView(view.width, view.height)) {
line.draw(ctx);
if (Polyline.isInsideView(l.x1, l.y1, l.x2, l.y2, view.width, view.height)) {
_drawLine(l, ctx);
}
};
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (index === 0) {
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.moveTo(l.x1, l.y1);
} else {
ctx.lineTo(line.x1, line.y1);
ctx.lineTo(l.x1, l.y1);
}
return true;
};
}
} else if (view.projection === ProjectionEnum.HPX) {
/*} else if (view.projection === ProjectionEnum.HPX) {
drawLine = (v0, v1) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
@@ -346,31 +375,31 @@ export let Polyline= (function() {
return false;
}
};
}
}*/
} else {
drawLine = (v0, v1) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (line.isInsideView(view.width, view.height)) {
const mag2 = _calculateMag2ForNoSinProjections(line, view);
if (Polyline.isInsideView(l.x1, l.y1, l.x2, l.y2, view.width, view.height)) {
const mag2 = _calculateMag2ForNoSinProjections(l, view);
if (mag2 < 0.1) {
line.draw(ctx);
_drawLine(l, ctx);
}
}
};
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
const line = new Line(v0.x, v0.y, v1.x, v1.y);
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
const mag2 = _calculateMag2ForNoSinProjections(line, view);
const mag2 = _calculateMag2ForNoSinProjections(l, view);
if (mag2 < 0.1) {
if (index === 0) {
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.moveTo(l.x1, l.y1);
} else {
ctx.lineTo(line.x1, line.y1);
ctx.lineTo(l.x1, l.y1);
}
return true;
@@ -415,13 +444,15 @@ export let Polyline= (function() {
v1 = v1 + 1;
}
ctx.globalAlpha = 1;
//ctx.globalAlpha = 1;
ctx.save();
ctx.fillStyle = this.fillColor;
ctx.globalAlpha = this.opacity;
ctx.fill();
ctx.restore();
}
return true;
};
Polyline.prototype.isInStroke = function(ctx, view, x, y) {
@@ -429,7 +460,7 @@ export let Polyline= (function() {
let pointXY = [];
for (var j = 0; j < this.radecArray.length; j++) {
var xy = AladinUtils.radecToViewXy(this.radecArray[j][0], this.radecArray[j][1], view.aladin);
var xy = view.aladin.world2pix(this.radecArray[j][0], this.radecArray[j][1]);
if (!xy) {
return false;
}
@@ -441,8 +472,8 @@ export let Polyline= (function() {
const lastPointIdx = pointXY.length - 1;
for (var l = 0; l < lastPointIdx; l++) {
const line = new Line(pointXY[l].x, pointXY[l].y, pointXY[l + 1].x, pointXY[l + 1].y); // new segment
line.draw(ctx, true);
const line = {x1: pointXY[l].x, y1: pointXY[l].y, x2: pointXY[l + 1].x, y2: pointXY[l + 1].y}; // new segment
_drawLine(line, ctx, true);
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
@@ -450,8 +481,8 @@ export let Polyline= (function() {
}
if(this.closed) {
const line = new Line(pointXY[lastPointIdx].x, pointXY[lastPointIdx].y, pointXY[0].x, pointXY[0].y); // new segment
line.draw(ctx, true);
const line = {x1: pointXY[lastPointIdx].x, y1: pointXY[lastPointIdx].y, x2: pointXY[0].x, y2: pointXY[0].y}; // new segment
_drawLine(line, ctx, true);
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
@@ -465,5 +496,44 @@ export let Polyline= (function() {
// todo
};
// static methods
// Method for testing whether a line is inside the view
// http://www.jeffreythompson.org/collision-detection/line-rect.php
Polyline.isInsideView = function(x1, y1, x2, y2, rw, rh) {
if (x1 >= 0 && x1 <= rw && y1 >= 0 && y1 <= rh) {
return true;
}
if (x2 >= 0 && x2 <= rw && y2 >= 0 && y2 <= rh) {
return true;
}
// check if the line has hit any of the rectangle's sides
// uses the Line/Line function below
let left = Polyline._intersectLine(x1, y1, x2, y2, 0, 0, 0, rh);
let right = Polyline._intersectLine(x1, y1, x2, y2, rw, 0, rw, rh);
let top = Polyline._intersectLine(x1, y1, x2, y2, 0, 0, rw, 0);
let bottom = Polyline._intersectLine(x1, y1, x2, y2, 0, rh, rw, rh);
// if ANY of the above are true, the line
// has hit the rectangle
if (left || right || top || bottom) {
return true;
}
return false;
};
Polyline._intersectLine = function(x1, y1, x2, y2, x3, y3, x4, y4) {
// Calculate the direction of the lines
let uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
let uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
// If uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
return true;
}
return false;
};
return Polyline;
})();

View File

@@ -90,7 +90,6 @@ export let Popup = (function() {
}
source.popup = this;
this.source = source;
this.setPosition(source.x, source.y);
};

View File

@@ -33,7 +33,6 @@ import { Color } from "./Color.js";
import { Coo } from "./libs/astro/coo.js";
import { Utils } from "./Utils";
import { CooFrameEnum } from "./CooFrameEnum.js";
// TODO: index sources according to their HEALPix ipix
// TODO : merge parsing with class Catalog
export let ProgressiveCat = (function() {
@@ -74,20 +73,11 @@ export let ProgressiveCat = (function() {
this.onClick = options.onClick || undefined; // TODO: inherit from catalog
// we cache the list of sources in each healpix tile. Key of the cache is norder+'-'+npix
this.sourcesCache = new Utils.LRUCache(256);
this.footprintsCache = new Utils.LRUCache(256);
//added to allow hips catalogue to also use shape functions
if (this.shape instanceof Image || this.shape instanceof HTMLCanvasElement) {
this.sourceSize = this.shape.width;
}
this._shapeIsFunction = false; // if true, the shape is a function drawing on the canvas
if (typeof this.shape === 'function') {
this._shapeIsFunction = true;
}
this.updateShape(options);
this.maxOrderAllsky = 2;
@@ -219,7 +209,9 @@ export let ProgressiveCat = (function() {
sources.push(newSource);
newSource.setCatalog(instance);
}
return sources;
let footprints = instance.computeFootprints(sources);
return [sources, footprints];
};
ProgressiveCat.prototype = {
@@ -272,7 +264,10 @@ export let ProgressiveCat = (function() {
url: self.rootUrl + '/' + 'Norder1/Allsky.tsv',
method: 'GET',
success: function(tsv) {
self.order1Sources = getSources(self, tsv, self.fields);
let [sources, footprints] = getSources(self, tsv, self.fields);
self.order1Footprints = footprints;
self.order1Sources = sources;
if (self.order2Sources) {
self.isReady = true;
@@ -289,7 +284,10 @@ export let ProgressiveCat = (function() {
url: self.rootUrl + '/' + 'Norder2/Allsky.tsv',
method: 'GET',
success: function(tsv) {
self.order2Sources = getSources(self, tsv, self.fields);
let [sources, footprints] = getSources(self, tsv, self.fields);
self.order2Footprints = footprints;
self.order2Sources = sources;
if (self.order1Sources) {
self.isReady = true;
@@ -319,7 +317,12 @@ export let ProgressiveCat = (function() {
let xml = ProgressiveCat.parser.parseFromString(text, "text/xml")
self.fields = getFields(self, xml);
self.order2Sources = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
let [sources, footprints] = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
self.order2Footprints = footprints
self.order2Sources = sources
if (self.order3Sources) {
self.isReady = true;
self._finishInitWhenReady();
@@ -339,7 +342,10 @@ export let ProgressiveCat = (function() {
method: 'GET',
success: function(text) {
let xml = ProgressiveCat.parser.parseFromString(text, "text/xml")
self.order3Sources = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
let [sources, footprints] = getSources(self, xml.querySelectorAll('CSV').innerText, self.fields);
self.order3Footprints = footprints
self.order3Sources = sources
if (self.order2Sources) {
self.isReady = true;
self._finishInitWhenReady();
@@ -356,7 +362,7 @@ export let ProgressiveCat = (function() {
this.loadNeededTiles();
},
draw: function(ctx, frame, width, height, largestDim) {
draw: function(ctx, width, height) {
if (! this.isShowing || ! this.isReady) {
return;
}
@@ -366,38 +372,67 @@ export let ProgressiveCat = (function() {
}
// Order must be >= 0
this.drawSources(this.order1Sources, ctx, width, height);
if (this.order1Sources) {
this.drawSources(this.order1Sources, ctx, width, height);
}
if (this.order1Footprints) {
this.order1Footprints.forEach((f) => {
f.draw(ctx, this.view)
});
}
if (this.view.realNorder >= 1) {
this.drawSources(this.order2Sources, ctx, width, height);
if (this.order2Sources) {
this.drawSources(this.order2Sources, ctx, width, height);
}
if (this.order2Footprints) {
this.order2Footprints.forEach((f) => {
f.draw(ctx, this.view)
});
}
}
// For old allsky, tilesInView refers to tiles at orders 4..
// For new allsky, tilesInView will contains order3 sources
if (this.maxOrderAllsky === 3) {
if (this.view.realNorder >= 2) {
this.drawSources(this.order3Sources, ctx, width, height);
if (this.order3Sources) {
this.drawSources(this.order3Sources, ctx, width, height);
}
if (this.order3Footprints) {
this.order3Footprints.forEach((f) => {
f.draw(ctx, this.view)
});
}
}
}
let key, sources;
let key, sources, footprints;
this.tilesInView.forEach((tile) => {
key = tile[0] + '-' + tile[1];
sources = this.sourcesCache.get(key);
footprints = this.footprintsCache.get(key);
if (sources) {
this.drawSources(sources, ctx, width, height);
}
if (footprints) {
footprints.forEach((f) => {
f.draw(ctx, this.view)
});
}
});
if (this._shapeIsFunction) {
ctx.restore();
}
},
drawSources: function(sources, ctx, width, height) {
if (!sources) {
return;
}
let ra = [], dec = [];
let ra = []
let dec = [];
sources.forEach((s) => {
ra.push(s.ra);
dec.push(s.dec);
@@ -445,7 +480,32 @@ export let ProgressiveCat = (function() {
return ret;
},
getFootprints: function() {
var ret = [];
if (this.order1Footprints) {
ret = ret.concat(this.order1Footprints);
}
if (this.order2Footprints) {
ret = ret.concat(this.order2Footprints);
}
if (this.order3Footprints) {
ret = ret.concat(this.order3Footprints);
}
if (this.tilesInView) {
var footprints, key, t;
for (var k=0; k < this.tilesInView.length; k++) {
t = this.tilesInView[k];
key = t[0] + '-' + t[1];
footprints = this.footprintsCache.get(key);
if (footprints) {
ret = ret.concat(footprints);
}
}
}
return ret;
},
deselectAll: function() {
if (this.order1Sources) {
@@ -474,6 +534,11 @@ export let ProgressiveCat = (function() {
for (var k=0; k<sources.length; k++) {
sources[k].deselect();
}
var footprints = this.footprintsCache[key];
for (var k=0; k<footprints.length; k++) {
footprints[k].deselect();
}
}
},
@@ -501,11 +566,6 @@ export let ProgressiveCat = (function() {
return this.rootUrl + "/" + "Norder" + norder + "/Dir" + dirIdx + "/Npix" + npix + ".tsv";
},
// todo, allow HiPS cats to support footprints
getFootprints: function() {
return null;
},
loadNeededTiles: function() {
if ( ! this.isShowing) {
return;
@@ -559,18 +619,23 @@ export let ProgressiveCat = (function() {
url: Aladin.JSONP_PROXY,
data: {"url": self.getTileURL(norder, ipix)},
*/
// ATTENTIOn : je passe en JSON direct, car je n'arrive pas a choper les 404 en JSONP
// ATTENTION : je passe en JSON direct, car je n'arrive pas a choper les 404 en JSONP
url: self.getTileURL(norder, ipix),
desc: "Get tile .tsv " + norder + ' ' + ipix + ' of ' + self.name,
method: 'GET',
//dataType: 'jsonp',
success: function(tsv) {
self.sourcesCache.set(key, getSources(self, tsv, self.fields));
let [sources, footprints] = getSources(self, tsv, self.fields);
self.sourcesCache.set(key, sources);
self.footprintsCache.set(key, footprints);
self.view.requestRedraw();
},
error: function() {
// on suppose qu'il s'agit d'une erreur 404
self.sourcesCache.set(key, []);
self.footprintsCache.set(key, []);
}
});
})(this, t[0], t[1]);
@@ -578,6 +643,8 @@ export let ProgressiveCat = (function() {
}
},
computeFootprints: Catalog.prototype.computeFootprints,
reportChange: function() { // TODO: to be shared with Catalog
this.view && this.view.requestRedraw();
}

View File

@@ -99,13 +99,14 @@ export class Selector {
if (view.catalogs) {
for (var k = 0; k < view.catalogs.length; k++) {
cat = view.catalogs[k];
if (!cat.isShowing) {
continue;
}
sources = cat.getSources();
for (var l = 0; l < sources.length; l++) {
s = sources[l];
if (!s.isShowing || !s.x || !s.y) {
if (!s.isShowing || !s.x || !s.y || s.tooSmallFootprint === false) {
continue;
}
if (selection.contains(s)) {

View File

@@ -62,7 +62,7 @@ export let SimbadPointer = (function() {
}
content += '<br><a target="_blank" href="http://cdsportal.u-strasbg.fr/?target=' + encodeURIComponent(objName) + '">Query in CDS portal</a>';
content += '</div>';
aladinInstance.showPopup(objCoo.lon, objCoo.lat, title, content);
}
else {

View File

@@ -125,6 +125,39 @@ Utils.isInt = function (n: string | number) {
return Utils.isNumber(n) && Math.floor(n as number) === n
}
// Newton-Raphson method to find the approximate inverse of f(x)
Utils.inverseNewtonRaphson = function(y: number, f: Function, fPrime: Function, tolerance=1e-6, maxIterations=100) {
let x_guess = 0.5; // Initial guess
let iteration = 0;
while (iteration < maxIterations) {
let f_x = f(x_guess);
let error = Math.abs(f_x - y);
if (error < tolerance) {
return x_guess; // Found approximate inverse
}
let derivative = fPrime(x_guess);
x_guess = x_guess - (f_x - y) / derivative; // Newton-Raphson update
iteration++;
}
return null; // No convergence within maxIterations
}
Utils.binarySearch = function(array, value) {
var low = 0,
high = array.length;
while (low < high) {
var mid = (low + high) >>> 1;
if (array[mid] > value) low = mid + 1;
else high = mid;
}
return low;
}
/* a debounce function, used to prevent multiple calls to the same function if less than delay milliseconds have passed */
Utils.debounce = function (fn, delay) {
var timer = null
@@ -310,17 +343,24 @@ Utils.getAjaxObject = function (url, method, dataType, useProxy) {
*/
Utils.fetch = function(params) {
let url = new URL(params.url);
if (params.useProxy === true) {
url = Utils.handleCORSNotSameOrigin(url)
}
if (params.data) {
// add the search params to the url object
for (const key in params.data) {
url.searchParams.append(key, params.data[key]);
let url;
try {
url = new URL(params.url);
if (params.useProxy === true) {
url = Utils.handleCORSNotSameOrigin(url)
}
if (params.data) {
// add the search params to the url object
for (const key in params.data) {
url.searchParams.append(key, params.data[key]);
}
}
} catch(e) {
// localhost url
url = params.url;
}
let request = new Request(url, {
method: params.method || 'GET',
@@ -429,6 +469,14 @@ Utils.fixURLForHTTPS = function (url) {
return url
}
Utils.isUrl = function(url) {
try {
return new URL(url).href;
} catch(e) {
return undefined;
}
}
// generate an absolute URL from a relative URL
// example: getAbsoluteURL('foo/bar/toto') return http://cds.unistra.fr/AL/foo/bar/toto if executed from page http://cds.unistra.fr/AL/
Utils.getAbsoluteURL = function (url) {

View File

@@ -43,7 +43,7 @@ import { CooFrameEnum } from "./CooFrameEnum.js";
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
import { WebGLCtx } from "./WebGL.js";
import { ALEvent } from "./events/ALEvent.js";
import { ColorCfg } from "./ColorCfg.js";
import { Zoom } from './Zoom.js'
import { Footprint } from "./Footprint.js";
import { Selector } from "./Selector.js";
import { ObsCore } from "./vo/ObsCore.js";
@@ -133,14 +133,11 @@ export let View = (function () {
lon = lat = 0;
// FoV init settings
const si = 500000.0;
const alpha = 40.0;
let initialFov = this.options.fov || 180.0;
this.pinchZoomParameters = {
isPinching: false, // true if a pinch zoom is ongoing
initialFov: undefined,
initialDistance: undefined,
initialAccDelta: Math.pow(si / initialFov, 1.0 / alpha)
};
// Projection definition
@@ -148,7 +145,8 @@ export let View = (function () {
this.setProjection(projName)
// Then set the zoom properly once the projection is defined
this.setZoom(initialFov);
this.wasm.setFieldOfView(initialFov);
this.updateZoomState();
// Target position settings
this.viewCenter = { lon, lat }; // position of center of view
@@ -216,6 +214,7 @@ export let View = (function () {
initialFingerAngle: undefined,
rotationInitiated: false
}
this.zoom = new Zoom(this);
this.fadingLatestUpdate = null;
this.dateRequestRedraw = null;
@@ -316,10 +315,10 @@ export let View = (function () {
imageCanvas.remove();
}
let gridCanvas = this.aladinDiv.querySelector('.aladin-gridCanvas');
/*let gridCanvas = this.aladinDiv.querySelector('.aladin-gridCanvas');
if (gridCanvas) {
gridCanvas.remove();
}
}*/
let catalogCanvas = this.aladinDiv.querySelector('.aladin-catalogCanvas')
if (catalogCanvas) {
@@ -339,7 +338,7 @@ export let View = (function () {
};
this.catalogCanvas = createCanvas('aladin-catalogCanvas');
this.gridCanvas = createCanvas('aladin-gridCanvas');
//this.gridCanvas = createCanvas('aladin-gridCanvas');
this.imageCanvas = createCanvas('aladin-imageCanvas');
};
@@ -369,13 +368,16 @@ export let View = (function () {
// reinitialize 2D context
this.catalogCtx = this.catalogCanvas.getContext("2d");
this.catalogCtx.canvas.width = this.width;
this.catalogCtx.canvas.height = this.height;
this.catalogCtx.canvas.width = this.width * window.devicePixelRatio;
this.catalogCtx.canvas.height = this.height * window.devicePixelRatio;
this.catalogCtx.canvas.style.width = this.width + "px";
this.catalogCtx.canvas.style.height = this.height + "px";
this.catalogCtx.scale(window.devicePixelRatio, window.devicePixelRatio);
this.gridCtx = this.gridCanvas.getContext("2d");
/*this.gridCtx = this.gridCanvas.getContext("2d");
this.gridCtx.canvas.width = this.width;
this.gridCtx.canvas.height = this.height;
*/
this.imageCtx = this.imageCanvas.getContext("webgl2");
this.imageCtx.canvas.style.width = this.width + "px";
this.imageCtx.canvas.style.height = this.height + "px";
@@ -467,9 +469,8 @@ export let View = (function () {
const canvas = this.wasm.canvas();
var c = document.createElement('canvas');
let dpi = window.devicePixelRatio;
c.width = width || (this.width * dpi);
c.height = height || (this.height * dpi);
c.width = width || (this.width * window.devicePixelRatio);
c.height = height || (this.height * window.devicePixelRatio);
var ctx = c.getContext('2d');
@@ -487,8 +488,10 @@ export let View = (function () {
View.prototype.selectLayer = function (layer) {
if (!this.imageLayers.has(layer)) {
throw layer + ' does not exists. So cannot be selected';
console.warn(layer + ' does not exists. So cannot be selected');
return;
}
this.selectedLayer = layer;
};
@@ -506,9 +509,8 @@ export let View = (function () {
view.unselectObjects()
try {
const lonlat = view.wasm.screenToWorld(xymouse.x, xymouse.y);
var radec = view.wasm.viewToICRSCooSys(lonlat[0], lonlat[1]);
view.pointTo(radec[0], radec[1]);
const [lon, lat] = view.aladin.pix2world(xymouse.x, xymouse.y, 'icrs');
view.pointTo(lon, lat);
}
catch (err) {
return;
@@ -664,8 +666,6 @@ export let View = (function () {
view.dragging = false;
view.pinchZoomParameters.isPinching = true;
//var fov = view.aladin.getFov();
//view.pinchZoomParameters.initialFov = Math.max(fov[0], fov[1]);
var fov = view.wasm.getFieldOfView();
view.pinchZoomParameters.initialFov = fov;
view.pinchZoomParameters.initialDistance = Math.sqrt(Math.pow(e.targetTouches[0].clientX - e.targetTouches[1].clientX, 2) + Math.pow(e.targetTouches[0].clientY - e.targetTouches[1].clientY, 2));
@@ -1061,13 +1061,21 @@ export let View = (function () {
var eventCount = 0;
var eventCountStart;
var isTouchPad;
var scale = 0.0;
let id;
Utils.on(view.catalogCanvas, 'wheel', function (e) {
e.preventDefault();
e.stopPropagation();
const xymouse = Utils.relMouseCoords(e);
view.wheelTriggered = true;
clearTimeout(id);
id = setTimeout(() => {
view.wheelTriggered = false;
}, 100);
const xymouse = Utils.relMouseCoords(e);
view.xy = xymouse
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
state: {
mode: view.mode,
@@ -1082,21 +1090,25 @@ export let View = (function () {
return;
}
var delta = e.deltaY || e.detail || (-e.wheelDelta);
if (!view.debounceProgCatOnZoom) {
var self = view;
view.debounceProgCatOnZoom = Utils.debounce(function () {
self.refreshProgressiveCats();
self.drawAllOverlays();
}, 300);
}
// Limit the minimum and maximum zoom levels
//var delta = e.deltaY;
// this seems to happen in context of Jupyter notebook --> we have to invert the direction of scroll
// hope this won't trigger some side effects ...
/*if (e.hasOwnProperty('originalEvent')) {
delta = -e.deltaY;
}*/
view.debounceProgCatOnZoom();
view.throttledZoomChanged();
// Zoom heuristic
// First detect the device
// See https://stackoverflow.com/questions/10744645/detect-touchpad-vs-mouse-in-javascript
// for detecting the use of a touchpad
var isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
if (!isTouchPadDefined) {
view.isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
if (!view.isTouchPadDefined) {
if (eventCount === 0) {
view.delta = 0;
eventCountStart = new Date().getTime();
}
@@ -1108,50 +1120,77 @@ export let View = (function () {
} else {
isTouchPad = false;
}
isTouchPadDefined = true;
view.isTouchPadDefined = true;
}
}
// The value of the field of view is determined
// inside the backend
const triggerZoom = (amount) => {
if (delta < 0.0) {
view.increaseZoom(amount);
} else {
view.decreaseZoom(amount);
}
};
if (isTouchPadDefined) {
let dt = performance.now() - view.then
let a0, a1;
// touchpad
if (isTouchPad) {
a1 = 0.002;
a0 = 0.0002;
} else {
a1 = 0.01;
a0 = 0.0004;
}
const alpha = Math.pow(view.fov / view.projection.fov, 0.5);
const lerp = a0 * alpha + a1 * (1.0 - alpha);
triggerZoom(lerp);
// only ensure the touch pad test has been done before zooming
if (!view.isTouchPadDefined) {
return false;
}
if (!view.debounceProgCatOnZoom) {
var self = view;
view.debounceProgCatOnZoom = Utils.debounce(function () {
self.refreshProgressiveCats();
self.drawAllOverlays();
}, 300);
}
// touch pad defined
view.delta = e.deltaY || e.detail || (-e.wheelDelta);
view.debounceProgCatOnZoom();
view.throttledZoomChanged();
if (isTouchPad) {
if (!view.throttledTouchPadZoom) {
let radec;
view.throttledTouchPadZoom = Utils.throttle(() => {
if (!view.zoom.isZooming && !view.wheelTriggered) {
// start zooming detected
radec = view.aladin.pix2world(view.xy.x, view.xy.y);
}
let amount = view.delta > 0 ? -Zoom.MAX_IDX_DELTA_PER_TROTTLE : Zoom.MAX_IDX_DELTA_PER_TROTTLE;
if (amount === 0)
return;
// change the zoom level
let newFov = Zoom.determineNextFov(view, amount);
view.zoom.apply({
stop: newFov,
duration: 300
});
//view.setZoom(newFov)
/*if (amount > 0 && radec) {
let sRaDec = view.aladin.getRaDec();
let moveTo = function() {
const t = 1 - (view.x - view.x1) / (view.x2 - view.x1);
let ra = (0.5 + t*0.5) * sRaDec[0] + (0.5 - t*0.5) * radec[0]
let dec = (0.5 + t*0.5) * sRaDec[1] + (0.5 - t*0.5) * radec[1]
view.aladin.gotoRaDec(ra, dec)
if (t >= 1e-2)
requestAnimFrame(moveTo)
}
//requestAnimFrame(moveTo)
}*/
}, 40);
}
view.throttledTouchPadZoom();
} else {
if (!view.throttledMouseScrollZoom) {
view.throttledMouseScrollZoom = Utils.throttle(() => {
const factor = 5
let newFov = view.delta > 0 ? view.fov * factor : view.fov / factor;
// standard mouse wheel zooming
newFov = Math.max(Math.min(newFov, Zoom.MAX), Zoom.MIN)
view.zoom.apply({
stop: newFov,
duration: 300
});
}, 30);
}
view.throttledMouseScrollZoom()
}
return false;
});
@@ -1207,12 +1246,11 @@ export let View = (function () {
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
// Drawing code
try {
this.moving = this.wasm.update(elapsedTime);
} catch (e) {
console.error(e)
}
//try {
this.moving = this.wasm.update(elapsedTime);
//} catch (e) {
// console.error(e)
//}
////// 2. Draw catalogues////////
const isViewRendering = this.wasm.isRendering();
@@ -1230,6 +1268,7 @@ export let View = (function () {
View.prototype.drawAllOverlays = function () {
var ctx = this.catalogCtx;
this.catalogCanvasCleared = false;
if (this.mustClearCatalog) {
ctx.clearRect(0, 0, this.width, this.height);
this.catalogCanvasCleared = true;
@@ -1246,7 +1285,7 @@ export let View = (function () {
for (var i = 0; i < this.catalogs.length; i++) {
var cat = this.catalogs[i];
cat.draw(ctx, this.cooFrame, this.width, this.height, this.largestDim);
cat.draw(ctx, this.width, this.height);
}
}
// draw popup catalog
@@ -1256,11 +1295,11 @@ export let View = (function () {
this.catalogCanvasCleared = true;
}
this.catalogForPopup.draw(ctx, this.cooFrame, this.width, this.height, this.largestDim);
this.catalogForPopup.draw(ctx, this.width, this.height);
// draw popup overlay layer
if (this.overlayForPopup.isShowing) {
this.overlayForPopup.draw(ctx, this.cooFrame, this.width, this.height, this.largestDim);
this.overlayForPopup.draw(ctx);
}
}
@@ -1301,7 +1340,22 @@ export let View = (function () {
}
}
// display grid labels
if (this.gridCfg.enabled && this.gridCfg.showLabels) {
if (!this.catalogCanvasCleared) {
ctx.clearRect(0, 0, this.width, this.height);
this.catalogCanvasCleared = true;
}
this.wasm.drawGridLabels();
}
if (this.mode === View.SELECT) {
if (!this.catalogCanvasCleared) {
ctx.clearRect(0, 0, this.width, this.height);
this.catalogCanvasCleared = true;
}
this.selector.dispatch('draw')
}
};
@@ -1320,8 +1374,7 @@ export let View = (function () {
View.prototype.getVisiblePixList = function (norder) {
var pixList = [];
let centerWorldPosition = this.wasm.screenToWorld(this.cx, this.cy);
const [lon, lat] = this.wasm.viewToICRSCooSys(centerWorldPosition[0], centerWorldPosition[1]);
let [lon, lat] = this.aladin.pix2world(this.cx, this.cy, 'icrs');
var radius = this.fov * 0.5 * this.ratio;
this.wasm.queryDisc(norder, lon, lat, radius).forEach(x => pixList.push(Number(x)));
@@ -1415,7 +1468,6 @@ export let View = (function () {
};
// Called for touchmove events
// initialAccDelta must be consistent with fovDegrees here
View.prototype.setZoom = function (fov) {
// limit the fov in function of the projection
fov = Math.min(fov, this.projection.fov);
@@ -1433,69 +1485,41 @@ export let View = (function () {
}
this.wasm.setFieldOfView(fov);
this.updateZoomState();
};
View.prototype.increaseZoom = function (amount) {
const si = 500000.0;
const alpha = 40.0;
let initialAccDelta = this.pinchZoomParameters.initialAccDelta + amount;
let new_fov = si / Math.pow(initialAccDelta, alpha);
if (new_fov < 0.00002777777) {
new_fov = 0.00002777777;
}
this.pinchZoomParameters.initialAccDelta = initialAccDelta;
this.setZoom(new_fov);
View.prototype.increaseZoom = function () {
this.zoom.apply({
stop: Zoom.determineNextFov(this, 6),
duration: 300
});
}
View.prototype.decreaseZoom = function (amount) {
const si = 500000.0;
const alpha = 40.0;
let initialAccDelta = this.pinchZoomParameters.initialAccDelta - amount;
if (initialAccDelta <= 0.0) {
initialAccDelta = 1e-3;
}
let new_fov = si / Math.pow(initialAccDelta, alpha);
if (new_fov >= this.projection.fov) {
new_fov = this.projection.fov;
}
this.pinchZoomParameters.initialAccDelta = initialAccDelta;
this.setZoom(new_fov);
View.prototype.decreaseZoom = function () {
this.zoom.apply({
stop: Zoom.determineNextFov(this, -6),
duration: 300
});
}
View.prototype.setRotation = function(rotation) {
this.wasm.setRotationAroundCenter(rotation);
}
View.prototype.setGridConfig = function (gridCfg) {
this.gridCfg = {...this.gridCfg, ...gridCfg};
this.wasm.setGridConfig(this.gridCfg);
View.prototype.setGridOptions = function (options) {
this.gridCfg = {...this.gridCfg, ...options};
this.wasm.setGridOptions(this.gridCfg);
// send events
/*if (this.gridCfg.hasOwnProperty('enabled')) {
if (this.gridCfg.enabled === true) {
ALEvent.COO_GRID_ENABLED.dispatchedTo(this.aladinDiv);
}
else {
ALEvent.COO_GRID_DISABLED.dispatchedTo(this.aladinDiv);
}
}*/
if (!this.gridCfg.enabled) {
this.mustClearCatalog = true;
}
ALEvent.COO_GRID_UPDATED.dispatchedTo(this.aladinDiv, this.gridCfg);
this.requestRedraw();
};
View.prototype.getGridConfig = function() {
View.prototype.getGridOptions = function() {
return this.gridCfg;
}
@@ -1503,11 +1527,6 @@ export let View = (function () {
// Get the new zoom values from the backend
let fov = this.wasm.getFieldOfView();
// Update the pinch zoom parameters consequently
const si = 500000.0;
const alpha = 40.0;
this.pinchZoomParameters.initialAccDelta = Math.pow(si / fov, 1.0 / alpha);
// Save it
this.fov = fov;
this.computeNorder();
@@ -1561,10 +1580,20 @@ export let View = (function () {
if (idxOverlayLayer == -1) {
// it does not exist so we add it to the stack
this.overlayLayers.push(layerName);
} else {
// it exists
let alreadyPresentImageLayer = this.imageLayers.get(layerName);
alreadyPresentImageLayer.added = false;
}
imageLayer.added = true;
this.imageLayers.set(layerName, imageLayer);
// select the layer if he is on top
if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
}
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
@@ -1661,11 +1690,6 @@ export let View = (function () {
this.imageLayers.delete(layer);
this.imageLayers.set(newLayer, imageLayer);
// Change the selected layer if this is the one renamed
/*if (this.selectedLayer === layer) {
this.selectedLayer = newLayer;
}*/
// Tell the layer hierarchy has changed
ALEvent.HIPS_LAYER_RENAMED.dispatchedTo(this.aladinDiv, { layer, newLayer });
}
@@ -1717,7 +1741,7 @@ export let View = (function () {
this.empty = true;
} else if (this.selectedLayer === layer) {
// If the layer removed was selected then we select the base layer
this.selectedLayer = 'base';
this.selectLayer(this.overlayLayers[this.overlayLayers.length - 1]);
}
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer });
@@ -1782,10 +1806,10 @@ export let View = (function () {
// Set the grid label format
if (this.cooFrame.label == "J2000d") {
this.setGridConfig({fmt: "HMS"});
this.setGridOptions({fmt: "HMS"});
}
else {
this.setGridConfig({fmt: "DMS"});
this.setGridOptions({fmt: "DMS"});
}
// Get the new view center position (given in icrs)
@@ -1840,11 +1864,11 @@ export let View = (function () {
ra = parseFloat(ra);
dec = parseFloat(dec);
if (isNaN(ra) || isNaN(dec)) {
if (!ra || !dec) {
return;
}
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
this.viewCenter.lat = dec;
//this.updateLocation({lon: this.viewCenter.lon, lat: this.viewCenter.lat});
// Put a javascript code here to do some animation
@@ -1888,7 +1912,13 @@ export let View = (function () {
this.catalogs = [];
this.overlays = [];
this.mocs = [];
this.allOverlayLayers.forEach((overlay) => {
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: overlay });
})
this.allOverlayLayers = [];
this.mustClearCatalog = true;
this.requestRedraw();
};
@@ -1912,7 +1942,7 @@ export let View = (function () {
this.overlays.splice(indexToDelete, 1);
}
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: layer });
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer });
this.mustClearCatalog = true;
this.requestRedraw();
@@ -1983,17 +2013,19 @@ export let View = (function () {
let closest = null;
footprints.forEach((footprint) => {
// Hidden footprints are not considered
let lineWidth = footprint.getLineWidth();
if (!footprint.source || !footprint.source.tooSmallFootprint) {
// Hidden footprints are not considered
let lineWidth = footprint.getLineWidth();
footprint.setLineWidth(10.0);
if (footprint.isShowing && footprint.isInStroke(ctx, this, x, y)) {
closest = footprint;
}
footprint.setLineWidth(lineWidth);
footprint.setLineWidth(10.0);
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
closest = footprint;
}
footprint.setLineWidth(lineWidth);
if (closest) {
return closest;
if (closest) {
return closest;
}
}
})
@@ -2025,8 +2057,9 @@ export let View = (function () {
if (this.catalogs) {
for (var k = 0; k < this.catalogs.length; k++) {
let catalog = this.catalogs[k];
let footprints = catalog.getFootprints();
let closest = this.closestFootprints(catalog.footprints, ctx, x, y);
let closest = this.closestFootprints(footprints, ctx, x, y);
if (closest) {
//ctx.lineWidth = pastLineWidth;
return [closest];

161
src/js/Zoom.js Normal file
View File

@@ -0,0 +1,161 @@
// 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 Tile
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
import { Utils } from "./Utils";
import { requestAnimFrame } from "./libs/RequestAnimationFrame.js";
export let Zoom = (function() {
// constructor
function Zoom(view) {
this.view = view;
};
Zoom.LEVELS = [
360, 330, 300, 275, 250, 225, 200, 190,
180, 170, 160, 150, 140, 130, 120, 110, 100,
95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 18, 16, 14, 12, 10,
9, 8, 7, 6, 5, 4, 3, 2, 1.75, 1.5, 1.25, 1,
55/60, 50/60, 45/60, 40/60, 35/60, 30/60, 25/60, 20/60, 15/60, 10/60,
9/60, 8/60, 7/60, 6/60, 5/60, 4/60, 3/60, 2/60, 1/60,
50/3600, 40/3600, 30/3600, 20/3600, 10/3600,
9/3600, 8/3600, 7/3600, 6/3600, 5/3600, 4/3600, 3/3600, 2/3600, 1/3600,
9/36000, 8/36000, 7/36000, 6/36000, 5/36000, 4/36000, 3/36000, 2/36000, 1/36000
];
Zoom.MAX_IDX_DELTA_PER_TROTTLE = 2;
Zoom.determineNextFov = function(view, amount) {
if (!view.idx)
view.idx = Utils.binarySearch(Zoom.LEVELS, view.fov);
let deltaIdx = amount;
view.idx += deltaIdx;
// clamp to the array indices
if (view.idx < 0) {
view.idx = 0
}
if (view.idx >= Zoom.LEVELS.length) {
view.idx = Zoom.LEVELS.length - 1
}
return Zoom.LEVELS[view.idx];
}
Zoom.prototype.apply = function(options) {
let startZoom = options['start'] || this.view.fov;
let finalZoom = options['stop'] || undefined;
let interpolationDuration = options['duration'] || 1000; // default to 1seconds
if (!finalZoom)
return;
this.finalZoom = finalZoom;
if (!this.isZooming) {
this.isZooming = true;
this.startTime = performance.now();
this.x1 = 0
this.x2 = 1;
this.y1 = startZoom;
this.y2 = finalZoom;
this.m1 = finalZoom - startZoom;
this.m2 = 0;
this.x = this.x1;
} else {
// find the startTime
this.x = (performance.now() - this.startTime) / interpolationDuration;
let m1 = Zoom.hermiteCubic.fPrime(this.x, this.x1, this.x2, this.y1, this.y2, this.m1, this.m2)
let y1 = Zoom.hermiteCubic.f(this.x, this.x1, this.x2, this.y1, this.y2, this.m1, this.m2);
this.y1 = y1;
this.x1 = this.x;
this.x2 = this.x1 + 1;
this.y2 = finalZoom;
this.m1 = m1;
this.m2 = 0;
}
// Initialize current zoom to the current zoom level
let interpolatedZoom;
let self = this;
// Recursive function to perform interpolation for each frame
function interpolateFrame() {
//console.log('zooming')
//fps = 1000 / self.dt;
//totalFrames = interpolationDuration * fps; // Total number of frames
self.x = ( performance.now() - self.startTime ) / interpolationDuration;
// Calculate step size for each frame
//stepSize = (desiredZoom - currentZoom) / totalFrames;
interpolatedZoom = Zoom.hermiteCubic.f(self.x, self.x1, self.x2, self.y1, self.y2, self.m1, self.m2);
// Clamp the interpolation in case it is < 0 for a time
interpolatedZoom = Math.max(Zoom.MIN, interpolatedZoom);
// Apply zoom level to map or perform any necessary rendering
self.view.setZoom(interpolatedZoom);
self.fov = interpolatedZoom;
// Check if interpolation is complete
if (self.x >= self.x2 || Math.abs(interpolatedZoom - self.finalZoom) < 1e-4) {
self.view.setZoom(self.finalZoom);
self.isZooming = false;
} else {
// Request the next frame
requestAnimFrame(interpolateFrame);
}
}
// Start interpolation by requesting the first frame
requestAnimFrame(interpolateFrame);
}
Zoom.MAX = Zoom.LEVELS[0];
Zoom.MIN = Zoom.LEVELS[Zoom.LEVELS.length - 1];
Zoom.hermiteCubic = {
f: function(x, x1, x2, y1, y2, m1, m2) {
let t = (x - x1) / (x2 - x1)
let t2 = t*t;
let t3 = t2*t;
return (1 - 3*t2 + 2*t3) * y1 + (t - 2*t2 + t3) * m1 + (3*t2 - 2*t3) * y2 + (-t2 + t3) * m2
},
fPrime: function(x, x1, x2, y1, y2, m1, m2) {
let t = (x - x1) / (x2 - x1)
let t2 = t*t;
return (1 / (x2 - x1))*((-6*t+6*t2)*y1 + (1 - 4*t + 3*t2)*m1 + (6*t - 6*t2)*y2 + m2*(3*t2 - 2*t))
}
}
return Zoom;
})();

View File

@@ -53,6 +53,8 @@ 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_LAYER_CHANGED = new ALEvent("AL:HiPSLayer.changed");
static GRAPHIC_OVERLAY_LAYER_ADDED = new ALEvent("AL:GraphicOverlayLayer.added");

View File

@@ -131,6 +131,7 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
}, aladin)
super({
close: false,
content: Layout.horizontal({
layout: [inputText, loadBtn]
}),
@@ -238,10 +239,10 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
this.loadBtn.update({disable: true}, aladin)
} else {
let self = this;
let layout = [];
let ctxMenu = [];
if (item && item.cs_service_url) {
layout.push({
ctxMenu.push({
label: 'Cone search',
disable: !item.cs_service_url,
action(o) {
@@ -263,6 +264,8 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
})
self._hide();
self.callback && self.callback();
},
position: {
anchor: 'center center',
@@ -270,13 +273,12 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
})
self.box._show();
self.loadBtn.hideMenu()
}
})
}
if (item && item.hips_service_url) {
layout.push({
ctxMenu.push({
label: 'HiPS catalogue',
disable: !item.hips_service_url,
action(o) {
@@ -286,15 +288,22 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
})
self._hide();
self.callback && self.callback();
}
})
}
this.loadBtn.update({ctxMenu: layout, disable: false}, aladin)
this.loadBtn.update({ctxMenu, disable: false}, aladin)
}
this.loadBtn.hideMenu()
}
attach(options) {
this.callback = options.callback;
super.update(options)
}
_hide() {
if (this.box) {
this.box.remove();

View File

@@ -79,6 +79,7 @@ import { Input } from "../Widgets/Input.js";
super(
{
close: false,
content: Layout.horizontal({
layout: [
inputText,

View File

@@ -40,15 +40,15 @@ import { ColorCfg } from "../../ColorCfg.js";
import { Layout } from "../Layout.js";
import { Input } from "../Widgets/Input.js";
export class LayerEditBox extends Box {
export class HiPSSettingsBox extends Box {
// Constructor
constructor(aladin, options) {
super(
{
super({
cssStyle: {
padding: '4px',
backgroundColor: 'black',
},
close: false,
...options
},
aladin.aladinDiv
@@ -162,21 +162,19 @@ import { ColorCfg } from "../../ColorCfg.js";
let layerOpacity = layer.getOpacity()
self.opacitySettingsContent = Layout.horizontal([
Input.slider({
tooltip: {content: layerOpacity, position: {direction: 'bottom'}},
name: 'opacitySlider',
type: 'range',
min: 0.0,
max: 1.0,
value: layerOpacity,
change(e, slider) {
const opacity = +e.target.value;
layer.setOpacity(opacity)
slider.update({value: opacity, tooltip: {content: opacity.toFixed(2), position: {direction: 'bottom'}}})
}
}),
]);
self.opacitySettingsContent = Input.slider({
tooltip: {content: layerOpacity, position: {direction: 'bottom'}},
name: 'opacitySlider',
type: 'range',
min: 0.0,
max: 1.0,
value: layerOpacity,
change(e, slider) {
const opacity = +e.target.value;
layer.setOpacity(opacity)
slider.update({value: opacity, tooltip: {content: opacity.toFixed(2), position: {direction: 'bottom'}}})
}
})
let brightness = layer.getColorCfg().getBrightness()
let saturation = layer.getColorCfg().getSaturation()
@@ -311,10 +309,10 @@ import { ColorCfg } from "../../ColorCfg.js";
_addListeners() {
ALEvent.HIPS_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, (e) => {
const layerChanged = e.detail.layer;
const hips = e.detail.layer;
let selectedLayer = this.options.layer;
if (selectedLayer && layerChanged.layer === selectedLayer.layer) {
let colorCfg = layerChanged.getColorCfg();
if (selectedLayer && hips.layer === selectedLayer.layer) {
let colorCfg = hips.getColorCfg();
let cmap = colorCfg.getColormap();
let reversed = colorCfg.getReversed();
@@ -323,7 +321,9 @@ import { ColorCfg } from "../../ColorCfg.js";
let [minCut, maxCut] = colorCfg.getCuts();
this.minCutInput.set(+minCut.toFixed(2));
this.maxCutInput.set(+maxCut.toFixed(2));
this.stretchSelector.update({value: stretch})
this.stretchSelector.update({value: stretch});
this.opacitySettingsContent.set(hips.getOpacity())
}
});
}

View File

@@ -35,6 +35,7 @@ export class ShortLivedBox extends Box {
constructor(aladin) {
super(
{
close: false,
cssStyle: {
color: 'white',
backgroundColor: 'black',

874
src/js/gui/Box/StackBox.js Normal file
View File

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

View File

@@ -38,7 +38,7 @@ import { Icon } from "../Widgets/Icon";
export class StatusBarBox extends Box {
constructor(aladin, options) {
super(options, aladin.aladinDiv)
super({...options, close: false}, aladin.aladinDiv)
this.addClass("aladin-status-bar");

View File

@@ -20,6 +20,8 @@
import { CtxMenuActionButtonOpener } from "./CtxMenuOpener";
import stackOverlayIconUrl from './../../../../assets/icons/stack.svg';
import { OverlayStack } from "../CtxMenu/OverlayStack";
import { OverlayStackBox } from "../Box/StackBox";
import { TogglerActionButton } from "./Toggler";
/******************************************************************************
* Aladin Lite project
*
@@ -35,14 +37,15 @@ import { OverlayStack } from "../CtxMenu/OverlayStack";
* Class representing a Tabs layout
* @extends CtxMenuActionButtonOpener
*/
export class OverlayStackButton extends CtxMenuActionButtonOpener {
export class OverlayStackButton extends TogglerActionButton {
/**
* UI responsible for displaying the viewport infos
* @param {Aladin} aladin - The aladin instance.
*/
constructor(aladin, options) {
let self;
let stack = new OverlayStack(aladin);
let stack = new OverlayStackBox(aladin);
super({
icon: {
size: 'medium',
@@ -56,7 +59,18 @@ import { OverlayStack } from "../CtxMenu/OverlayStack";
direction: 'top right'
}
},
ctxMenu: stack,
toggled: false,
actionOn: (e) => {
stack._show({
position: {
nextTo: self,
direction: 'right'
}
})
},
actionOff: (e) => {
stack._hide()
},
...options
}, aladin);

View File

@@ -43,20 +43,26 @@ export class TogglerActionButton extends ActionButton {
...options,
toggled,
action(o) {
toggled = !toggled;
self.update({toggled, tooltip: toggled ? options.tooltipOn : options.tooltipOff})
if (toggled && options.actionOn) {
options.actionOn(o)
}
if (!toggled && options.actionOff) {
options.actionOff(o)
}
options.action && options.action(o)
self.toggle(o);
}
})
this.toggled = toggled;
self = this;
}
toggle(o) {
this.toggled = !this.toggled;
if (this.toggled && this.options.actionOn) {
this.options.actionOn(o)
}
if (!this.toggled && this.options.actionOff) {
this.options.actionOff(o)
}
// once the actions has been executed, modify the styling
this.update({toggled: this.toggled, tooltip: this.toggled ? this.options.tooltipOn : this.options.tooltipOff})
}
}

View File

@@ -35,7 +35,7 @@ import { CatalogQueryBox } from "../Box/CatalogQueryBox.js";
import A from "../../A.js";
import { Utils } from "../../../js/Utils";
import { View } from "../../View.js";
import { LayerEditBox } from "../Box/SurveyEditBox.js";
import { HiPSSettingsBox } from "../Box/HiPSSettingsBox.js";
import { HiPSSelectorBox } from "../Box/HiPSSelectorBox.js";
import searchIconUrl from '../../../../assets/icons/search.svg';
import showIconUrl from '../../../../assets/icons/show.svg';
@@ -297,7 +297,7 @@ export class OverlayStack extends ContextMenu {
let radius = self.aladin.angularDist(c.x, c.y, c.x + c.r, c.y);
// the moc needs a
let moc = A.MOCFromCircle(
let moc = A.MOCFromCone(
{ra, dec, radius},
{name: 'cone', lineWidth: 3.0},
);
@@ -456,70 +456,6 @@ export class OverlayStack extends ContextMenu {
}
}
layout.push({
label: 'Add survey',
subMenu: [
{
label: {
icon: {
url: searchIconUrl,
monochrome: true,
tooltip: {content: 'From our database...', position: { direction: 'right' }},
cssStyle: {
cursor: 'help',
},
},
content: 'Search for a survey'
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
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.show();
}
);
self.hipsSelectorBox._show({
position: self.position,
});
self.mode = 'hips';
}
},
ContextMenu.fileLoaderItem({
label: 'FITS image file',
accept: '.fits',
action(file) {
let url = URL.createObjectURL(file);
const image = self.aladin.createImageFITS(
url,
file.name,
undefined,
(ra, dec, fov, _) => {
// Center the view around the new fits object
self.aladin.gotoRaDec(ra, dec);
self.aladin.setFoV(fov * 1.1);
//self.aladin.selectLayer(image.layer);
},
undefined
);
self.aladin.setOverlayImageLayer(image, Utils.uuidv4())
}
}),
]
})
// survey list
let selectedLayer = self.aladin.getSelectedLayer();
@@ -669,7 +605,7 @@ export class OverlayStack extends ContextMenu {
let item = Layout.horizontal({
layout: [
'<div class="' + layerClassName + '" style="background-color: rgba(0, 0, 0, 0.6); line-height: 1rem; padding: 3px; border-radius: 3px; word-break: break-word;' + (selectedLayer === layer.layer ? 'border: 1px solid white;' : '') + '">' + (layer.name) + '</div>',
Layout.horizontal({layout: btns})
Layout.horizontal(btns)
],
/*cssStyle: {
display: 'flex',
@@ -699,7 +635,40 @@ export class OverlayStack extends ContextMenu {
};
l.subMenu = [];
l.subMenu.push({
label: {
icon: {
url: searchIconImg,
monochrome: true,
tooltip: {content: 'Find a specific survey <br /> in our database...', position: { direction: 'bottom' }},
cssStyle: {
cursor: 'help',
},
},
content: 'More...'
},
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
self.hipsBox = new HiPSSelectorBox(self.aladin)
self.hipsBox.attach((HiPSId) => {
self.aladin.setOverlayImageLayer(HiPSId, layer.layer);
self.show();
});
self.hipsBox._show({
position: self.position,
})
self.mode = 'hips';
}
})
for(const [id, ll] of defaultLayers) {
backgroundUrl = OverlayStack.previewImagesUrl[ll.name];
if (!backgroundUrl) {
@@ -732,41 +701,6 @@ export class OverlayStack extends ContextMenu {
})
}
l.subMenu.push({
label: {
icon: {
url: searchIconImg,
monochrome: true,
tooltip: {content: 'Find a specific survey <br /> in our database...', position: { direction: 'top' }},
cssStyle: {
cursor: 'help',
},
},
content: 'More...'
},
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
self.hipsBox = new HiPSSelectorBox(self.aladin)
self.hipsBox.attach(
(HiPSId) => {
self.aladin.setOverlayImageLayer(HiPSId, layer.layer);
self.show();
}
);
self.hipsBox._show({
position: self.position,
})
self.mode = 'hips';
}
})
l.action = (o) => {
let oldLayerClassName = 'a' + self.aladin.getSelectedLayer().replace(/[.\/ ]/g, '')
self.el.querySelector('.' + oldLayerClassName).style.removeProperty('border')

View File

@@ -42,29 +42,25 @@ export class FoV extends DOMElement {
// constructor
constructor(aladin, options) {
let layout = [];
if (options.showZoomControl) {
layout.push(new ActionButton({
let zoomIn = new ActionButton({
classList: 'aladin-zoom-in',
size: 'small',
tooltip: {content: 'zoom in', position: {direction: 'top'}},
icon: {
monochrome: true,
size: 'small',
url: plusIconUrl,
},
cssStyle: {
marginRight: 0,
borderRight: 'none',
borderRadius: '5px 0px 0px 5px'
},
action(o) {
aladin.increaseZoom();
}
}))
layout.push(new ActionButton({
})
let zoomOut = new ActionButton({
size: 'small',
cssStyle: {
borderRadius: '0px 5px 5px 0px'
},
classList: 'aladin-zoom-out',
tooltip: {content: 'zoom out', position: {direction: 'top'}},
icon: {
monochrome: true,
size: 'small',
@@ -73,7 +69,12 @@ export class FoV extends DOMElement {
action(o) {
aladin.decreaseZoom();
}
}))
});
zoomIn.el.classList.add('aladin-zoom-in');
zoomOut.el.classList.add('aladin-zoom-out');
layout.push(zoomIn)
layout.push(zoomOut)
}
if (options.showFov) {
@@ -82,10 +83,10 @@ export class FoV extends DOMElement {
'<div class="aladin-monospace-text"></div>'])
}
let el = Layout.horizontal({layout, tooltip: { content: 'FoV', position: {direction: "top"}}});
if (el.tooltip) {
el.tooltip.addClass('aladin-fov');
el.tooltip.addClass('aladin-dark-theme')
let el = Layout.horizontal({layout});
if (el) {
el.addClass('aladin-fov');
el.addClass('aladin-dark-theme')
}
super(el)

View File

@@ -0,0 +1,149 @@
// 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";
/******************************************************************************
* 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 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;
super({
name: 'HiPS search',
type: 'text',
classList: ['search'],
name: 'survey',
placeholder: "Survey keywords or url",
autocomplete: {options: Object.keys(HiPSSearch.HiPSList)},
title: layer.name,
actions: {
change(e) {
const key = e.target.value;
if (!key) {
self.update({value: prevKey, title: prevKey});
return;
}
let image;
// A user can put an url
try {
image = new URL(key).href;
} catch(e) {
// Or he can select a HiPS from the list given
let hips = HiPSSearch.HiPSList[key]
//console.log("HIPS", key, hips)
if (hips) {
image = hips.id || hips.url || undefined;
} else {
// Finally if not found, interpret the input text value as the HiPS (e.g. ID)
image = key;
}
}
self.el.blur();
if (image) {
prevKey = key;
aladin.setOverlayImageLayer(image, layer.layer);
}
},
/*input(e) {
let value = e.target.value;
self.update({value, title: value})
}*/
},
value: layer.name,
...options
})
this.addClass('aladin-HiPS-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

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { CooConversion } from "../CooConversion.js";
import { Coo } from "../libs/astro/coo.js";
import { CooFrameEnum } from "../CooFrameEnum.js";
@@ -146,7 +146,15 @@ export class Location extends DOMElement {
let param = e.detail;
if (param.type === 'mouseout') {
let [lon, lat] = aladin.getRaDec();
let radec = aladin.getRaDec();
// convert to the view frame
let lonlat = radec;
if (aladin.getFrame() === "Galactic") {
lonlat = CooConversion.J2000ToGalactic(radec)
}
let [lon, lat] = lonlat;
self.update({
lon, lat,
frame: aladin.view.cooFrame,

View File

@@ -15,28 +15,4 @@ Element.prototype.swap = function (node) {
// Move `node` to before the sibling of `this`
parent.insertBefore(node, sibling);
};
export let Utils = {}
/**
* Append el to target
*
* target must be an DOM Element/Node
*
* @API
*
* @param el: el can be a Widget or Element object. Otherwise it is considered as text
* @param target: target must be an DOM Element/Node
*
*/
Utils.binarySearch = function(array, value) {
var low = 0,
high = array.length;
while (low < high) {
var mid = (low + high) >>> 1;
if (array[mid] < value) low = mid + 1;
else high = mid;
}
return low;
}
};

View File

@@ -67,6 +67,25 @@ export class Box extends DOMElement {
let self = this;
let close = this.options.close === false ? false : true;
if (close) {
new ActionButton({
size: 'small',
content: '❌',
//tooltip: {content: 'Close the window', position: {direction: 'bottom'}},
action(e) {
self._hide();
},
cssStyle: {
position: 'absolute',
},
position: {
top: 0,
right: 0,
}
}, this.el);
}
// Check for the title
if (this.options.header) {
let header = this.options.header;
@@ -98,23 +117,11 @@ export class Box extends DOMElement {
titleEl.style.cursor = 'move'
}
let closedEl = new ActionButton({
size: 'small',
content: '❌',
tooltip: {content: 'Close the window', position: {direction: 'bottom'}},
cssStyle: {
cursor: 'pointer',
},
action(e) {
self._hide();
}
});
Layout.horizontal({
cssStyle: {
justifyContent: 'space-between',
},
layout: [draggableEl, titleEl, closedEl]
layout: [draggableEl, titleEl]
}, this.el);
let separatorEl = document.createElement('div')

View File

@@ -121,11 +121,12 @@ export class ContextMenu extends DOMElement {
monochrome: true,
url: copyIconUrl,
size: 'small',
tooltip: {content: 'Copy the position!', position: {direction: 'bottom'}}
cssStyle: {
cursor: 'not-allowed',
}
}),
posStr
]).attachTo(item)
} catch (e) {
item.innerHTML = '<span>Out of projection</span>';
}
@@ -240,11 +241,13 @@ export class ContextMenu extends DOMElement {
if (!opt.disabled || opt.disabled === false) {
if (!opt.subMenu || opt.subMenu.length === 0) {
if ((opt.mustHide === undefined || opt.mustHide === true) && (!self.options || self.options.hideOnClick === undefined || self.options.hideOnClick === true)) {
let close = opt.action(e, self);
close = close !== undefined ? close : true;
if (close && ((opt.mustHide === undefined || opt.mustHide === true) && (!self.options || self.options.hideOnClick === undefined || self.options.hideOnClick === true))) {
self._hide();
}
opt.action(e, self);
}
}
});

View File

@@ -135,7 +135,7 @@ export class Input extends DOMElement {
}
this.el.appendChild(datalist);
this.el.autocomplete = 'on';
this.el.autocomplete = 'off';
} else {
this.el.autocomplete = autocomplete;
}
@@ -199,6 +199,10 @@ export class Input extends DOMElement {
this.el.name = this.options.name;
}
if (this.options.title) {
this.el.title = this.options.title;
}
this.el.classList.add('aladin-input');
this.el.classList.add('aladin-dark-theme');

View File

@@ -30,7 +30,6 @@
*
*****************************************************************************/
import { Utils } from "../Utils.js";
import { DOMElement } from "./Widget.js";
export class Table extends DOMElement {

View File

@@ -146,9 +146,9 @@ export class DOMElement {
}
const aladinDiv = options && options.aladin && options.aladin.aladinDiv;
if (!aladinDiv) {
return;
}
let innerWidth = aladinDiv && aladinDiv.offsetWidth;
let innerHeight = aladinDiv && aladinDiv.offsetHeight;
let left, top, bottom, right;
let x, y;
@@ -156,11 +156,8 @@ export class DOMElement {
// handle the anchor/dir case with higher priority
const {offsetWidth, offsetHeight} = el;
const innerWidth = aladinDiv.offsetWidth;
const innerHeight = aladinDiv.offsetHeight;
// take on less priority the left and top
if (options && (options.left || options.top || options.right || options.bottom)) {
if (options && (options.left !== undefined || options.top !== undefined || options.right !== undefined || options.bottom !== undefined)) {
el.style.position = 'absolute';
if (options.top !== undefined) {
@@ -177,7 +174,7 @@ export class DOMElement {
}
if (typeof top === 'number') {
if (top + offsetHeight >= innerHeight) {
if (innerHeight && top + offsetHeight >= innerHeight) {
y = '-' + (top + offsetHeight - innerHeight) + 'px';
} else if (top < 0) {
y = Math.abs(top) + 'px';
@@ -189,7 +186,7 @@ export class DOMElement {
bottom = bottom + 'px';
}
if (typeof left === 'number') {
if (left + offsetWidth > innerWidth) {
if (innerWidth && left + offsetWidth > innerWidth) {
x = '-' + (left + offsetWidth - innerWidth) + 'px';
} else if (left < 0) {
x = Math.abs(left) + 'px';
@@ -304,7 +301,7 @@ export class DOMElement {
attachTo(target, position = 'beforeend') {
if(target) {
if (typeof position === 'number') {
target.insertChildAtIndex(this.element(), position)
target.insertBefore(this.element(), target.childNodes[position]);
} else {
target.insertAdjacentElement(position, this.element());
}