mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-25 04:16:55 -08:00
Compare commits
9 Commits
fix-26-col
...
proper_mot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c08833f8a | ||
|
|
7f83ccc04e | ||
|
|
56cb5c0dba | ||
|
|
53d44de229 | ||
|
|
3dcdeeec0c | ||
|
|
a1434f781b | ||
|
|
aa09263c04 | ||
|
|
9af2d835ff | ||
|
|
dc51517758 |
@@ -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
5
assets/icons/add.svg
Normal 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 |
@@ -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"});
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
45
examples/al-cat-galaxy-shape.html
Normal file
45
examples/al-cat-galaxy-shape.html
Normal 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>
|
||||
117
examples/al-cat-proper-motion.html
Normal file
117
examples/al-cat-proper-motion.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
32
examples/al-save-colormap.html
Normal file
32
examples/al-save-colormap.html
Normal 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>
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
if (searchParams.has('showCooGrid')) {
|
||||
const b = searchParams.get('showCooGrid') === 'true';
|
||||
aladin.view.setGridConfig({
|
||||
aladin.setCooGrid({
|
||||
enabled: b
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
if (searchParams.has('showCooGrid')) {
|
||||
const b = searchParams.get('showCooGrid') === 'true';
|
||||
aladin.view.setGridConfig({
|
||||
aladin.setCooGrid({
|
||||
enabled: b
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
if (searchParams.has('showCooGrid')) {
|
||||
const b = searchParams.get('showCooGrid') === 'true';
|
||||
aladin.view.setGridConfig({
|
||||
aladin.setCooGrid({
|
||||
enabled: b
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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> =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
197
src/js/A.js
197
src/js/A.js
@@ -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) {
|
||||
|
||||
233
src/js/Aladin.js
233
src/js/Aladin.js
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
212
src/js/Line.js
212
src/js/Line.js
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -90,7 +90,6 @@ export let Popup = (function() {
|
||||
}
|
||||
source.popup = this;
|
||||
this.source = source;
|
||||
|
||||
this.setPosition(source.x, source.y);
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
341
src/js/View.js
341
src/js/View.js
@@ -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
161
src/js/Zoom.js
Normal 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;
|
||||
})();
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -79,6 +79,7 @@ import { Input } from "../Widgets/Input.js";
|
||||
|
||||
super(
|
||||
{
|
||||
close: false,
|
||||
content: Layout.horizontal({
|
||||
layout: [
|
||||
inputText,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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
874
src/js/gui/Box/StackBox.js
Normal 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';
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
149
src/js/gui/Input/HiPSSearch.js
Normal file
149
src/js/gui/Input/HiPSSearch.js
Normal 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})
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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')
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
import { Utils } from "../Utils.js";
|
||||
import { DOMElement } from "./Widget.js";
|
||||
|
||||
export class Table extends DOMElement {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user