mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-25 20:34:50 -08:00
Compare commits
27 Commits
3.3.0
...
proper_mot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c08833f8a | ||
|
|
7f83ccc04e | ||
|
|
56cb5c0dba | ||
|
|
53d44de229 | ||
|
|
3dcdeeec0c | ||
|
|
a1434f781b | ||
|
|
aa09263c04 | ||
|
|
9af2d835ff | ||
|
|
dc51517758 | ||
|
|
8b8c1460eb | ||
|
|
f1a1247a43 | ||
|
|
54fcfe9f2b | ||
|
|
dfd91d9632 | ||
|
|
dc027e89c4 | ||
|
|
9617b233b0 | ||
|
|
7b2458ac8a | ||
|
|
02d97d7eba | ||
|
|
28c4a6144a | ||
|
|
e2e426493f | ||
|
|
f9f205f1d5 | ||
|
|
d070facc13 | ||
|
|
be0c84aa28 | ||
|
|
091effc92c | ||
|
|
cf26bd840c | ||
|
|
2062d6bfeb | ||
|
|
2a199aaf73 | ||
|
|
08a9c290ee |
6
.github/workflows/api_doc.yml
vendored
6
.github/workflows/api_doc.yml
vendored
@@ -4,8 +4,10 @@ name: Deploy static content to Pages
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["develop"]
|
||||
|
||||
tags:
|
||||
- '*'
|
||||
branches:
|
||||
- develop
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
16
.github/workflows/codemeta_validator.yml
vendored
Normal file
16
.github/workflows/codemeta_validator.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: test_codemeta
|
||||
|
||||
on:
|
||||
release:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: gitlab-registry.in2p3.fr/escape2020/wp3/eossr:v1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: validate codemeta
|
||||
run: eossr-metadata-validator codemeta.json
|
||||
3
.github/workflows/npm-publish.yml
vendored
3
.github/workflows/npm-publish.yml
vendored
@@ -6,6 +6,9 @@ name: Publish to NPM
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,18 @@
|
||||
# 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
|
||||
* [fixed] Detecting raytracing rendering mode. Adapt the rendering mode in function of the fov value and the projection used. Some projections do have more distortions with wide FoVs so it is better to use the raytracing rendering mode when fov >= smaller FoV threshold.
|
||||
|
||||
## 3.3.0
|
||||
|
||||
* [fixed] multiple calls to setImageSurvey with the same survey object led to strange behaviour.
|
||||
|
||||
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 |
83
codemeta.json
Normal file
83
codemeta.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"@context": "https://doi.org/10.5063/schema/codemeta-2.0",
|
||||
"@type": "SoftwareSourceCode",
|
||||
"license": "https://spdx.org/licenses/GPL-3.0",
|
||||
"codeRepository": "https://github.com/cds-astro/aladin-lite",
|
||||
"dateCreated": "2013-05-01",
|
||||
"datePublished": "2013-05-01",
|
||||
"dateModified": "2023-01-31",
|
||||
"issueTracker": "https://github.com/cds-astro/aladin-lite/issues",
|
||||
"name": "Aladin Lite",
|
||||
"version": "3.3.2",
|
||||
"softwareVersion": "3.3.2",
|
||||
"description": "An astronomical HiPS visualizer in the browser.",
|
||||
"identifier": "10.5281/zenodo.7638833",
|
||||
"applicationCategory": "Astronomy, Visualization",
|
||||
"funding": "ESCAPE 824064",
|
||||
"referencePublication": "http://aspbooks.org/publications/532/007.pdf",
|
||||
"readme": "https://aladin.cds.unistra.fr/AladinLite/doc/",
|
||||
"releaseNotes": "https://aladin.cds.unistra.fr/AladinLite/doc/#release-notes",
|
||||
"funder": {
|
||||
"@type": "Organization",
|
||||
"name": "European Commission Framework Programme Horizon 2020 Research and Innovation action"
|
||||
},
|
||||
"keywords": [
|
||||
"IVOA",
|
||||
"Astronomy"
|
||||
],
|
||||
"programmingLanguage": [
|
||||
"Rust",
|
||||
"Javascript"
|
||||
],
|
||||
"relatedLink": [
|
||||
"https://aladin.cds.unistra.fr/"
|
||||
],
|
||||
"author": [
|
||||
{
|
||||
"@type": "Person",
|
||||
"@id": "https://orcid.org/0000-0002-7123-773X",
|
||||
"givenName": "Matthieu",
|
||||
"familyName": "Baumann",
|
||||
"email": "matthieu.baumann@unistra.fr",
|
||||
"affiliation": {
|
||||
"@type": "Organization",
|
||||
"name": "Universit\u00e9 de Strasbourg, CNRS, Observatoire astronomique de Strasbourg, UMR 7550, F-67000 Strasbourg, France"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Person",
|
||||
"@id": "https://orcid.org/0000-0001-5818-2781",
|
||||
"givenName": "Thomas",
|
||||
"familyName": "Boch",
|
||||
"email": "thomas.boch@astro.unistra.fr",
|
||||
"affiliation": {
|
||||
"@type": "Organization",
|
||||
"name": "Universit\u00e9 de Strasbourg, CNRS, Observatoire astronomique de Strasbourg, UMR 7550, F-67000 Strasbourg, France"
|
||||
}
|
||||
}
|
||||
],
|
||||
"maintainer": [
|
||||
{
|
||||
"@type": "Person",
|
||||
"@id": "https://orcid.org/0000-0002-7123-773X",
|
||||
"givenName": "Matthieu",
|
||||
"familyName": "Baumann",
|
||||
"email": "matthieu.baumann@unistra.fr",
|
||||
"affiliation": {
|
||||
"@type": "Organization",
|
||||
"name": "Universit\u00e9 de Strasbourg, CNRS, Observatoire astronomique de Strasbourg, UMR 7550, F-67000 Strasbourg, France"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Person",
|
||||
"@id": "https://orcid.org/0000-0001-5818-2781",
|
||||
"givenName": "Thomas",
|
||||
"familyName": "Boch",
|
||||
"email": "thomas.boch@astro.unistra.fr",
|
||||
"affiliation": {
|
||||
"@type": "Organization",
|
||||
"name": "Universit\u00e9 de Strasbourg, CNRS, Observatoire astronomique de Strasbourg, UMR 7550, F-67000 Strasbourg, France"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
let aladin;
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin('#aladin-lite-div', {target: 'LMC', fov: 55, showContextMenu: true});
|
||||
var hips = A.catalogHiPS('https://axel.u-strasbg.fr/HiPSCatService/Simbad', {onClick: 'showPopup', name: 'Simbad'});
|
||||
var hips = A.catalogHiPS('https://axel.u-strasbg.fr/HiPSCatService/Simbad', {hoverColor: 'yellow', onClick: 'showPopup', name: 'Simbad'});
|
||||
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');
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
aladin.setBaseImageLayer("P/PanSTARRS/DR1/g", {imgFormat: "fits"});
|
||||
aladin.getBaseImageLayer().setColormap('redtemperature', {stretch: "Asinh"});*/
|
||||
|
||||
aladin = A.aladin('#aladin-lite-div', {survey: 'CDS/P/SDSS9/color', fov: 60, target: '0 90', fullScreen: true, cooFrame: 'equatorial', showCooGridControl: true, showSimbadPointerControl: true, showCooGrid: true});
|
||||
aladin.setProjection('SIN');
|
||||
aladin = A.aladin('#aladin-lite-div', {survey: 'CDS/P/SDSS9/color', fov: 150, target: '0 90', fullScreen: true, cooFrame: 'equatorial', showCooGridControl: true, showSimbadPointerControl: true, showCooGrid: true});
|
||||
|
||||
//let survey = aladin.createImageSurvey("P/PanSTARRS/DR1/g", null, null, null, null, );
|
||||
/*aladin.setBaseImageLayer("P/PanSTARRS/DR1/g");
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
samp: true,
|
||||
});
|
||||
|
||||
A.catalogFromSimbad('09 55 52.4 +69 40 47', 0.1, {onClick: 'showTable', limit: 1000}, (cat) => {
|
||||
aladin.addCatalog(cat)
|
||||
});
|
||||
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'}));
|
||||
});
|
||||
</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) => {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
var overlay = A.graphicOverlay({color: '#ee2345', lineWidth: 3});
|
||||
aladin.addOverlay(overlay);
|
||||
overlay.addFootprints([
|
||||
A.polygon([[83.64287, 22.01713], [83.59872, 22.01692], [83.59852, 21.97629], [83.64295, 21.97629]]),
|
||||
A.polygon([[83.64287, 22.01713], [83.59872, 22.01692], [83.59852, 21.97629], [83.64295, 21.97629]], {hoverColor: 'green'}),
|
||||
A.polygon([[83.62807, 22.06330], [83.58397, 22.02280], [83.62792, 22.02258]]),
|
||||
A.polygon([[8.62807, 220.06330], [83.58397, 10.02280], [150.62792, 87.02258]])
|
||||
]);
|
||||
@@ -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>
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, height=device-height, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">
|
||||
</head>
|
||||
<body>
|
||||
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
|
||||
<div id="aladin-lite-div" style="width: 500px; height: 500px"></div>
|
||||
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
|
||||
var aladin;
|
||||
A.init.then(() => {
|
||||
// Start up Aladin Lite
|
||||
let aladin = A.aladin('#aladin-lite-div', {target: 'Sgr a*', fov: 0.5, showContextMenu: true});
|
||||
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("CDS/P/2MASS6X/H");
|
||||
|
||||
let survey2 = aladin.newImageSurvey("CSIRO/P/RACS/mid/I");
|
||||
aladin.setImageLayer(survey2)
|
||||
survey2.setColormap('rdbu', {stretch: 'asinh'});
|
||||
|
||||
let survey3 = aladin.newImageSurvey("https://alasky.cds.unistra.fr/CFHTLS-T0007b/Deep/UALLSKY/");
|
||||
survey2.setColormap('rdbu', {stretch: 'linear'});
|
||||
|
||||
let survey3 = aladin.newImageSurvey("CSIRO/P/RACS/low/I");
|
||||
aladin.setImageLayer(survey3)
|
||||
survey3.setColormap('cubehelix', {stretch: 'asinh'});
|
||||
aladin.setImageLayer(survey3)
|
||||
aladin.setImageLayer(survey3)
|
||||
survey3.setColormap('cubehelix', {stretch: 'linear'});
|
||||
|
||||
aladin.setImageLayer(survey2);
|
||||
aladin.setImageLayer(survey3);
|
||||
aladin.setImageLayer(survey3);
|
||||
|
||||
//aladin.setOverlayImageLayer(survey2);
|
||||
aladin.setImageLayer(survey3)
|
||||
aladin.setImageLayer(survey2);
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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,10 @@
|
||||
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,
|
||||
showLayersControl: true,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
let aladin;
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin('#aladin-lite-div', {target: '00 00 00 +07 00 00', fov: 130, survey: 'P/Mellinger/color'});
|
||||
var moc11 = A.MOCFromURL('http://skies.esac.esa.int/HST/NICMOS/Moc.fits', {color: '#84f', lineWidth: 3, fill: true}, (moc) => {
|
||||
var moc11 = A.MOCFromURL('http://skies.esac.esa.int/HST/NICMOS/Moc.fits', {color: '#84f', lineWidth: 3, perimeter: true}, (moc) => {
|
||||
// moc is ready
|
||||
console.log(moc.contains(205.9019247, +2.4492764));
|
||||
console.log(moc.contains(-205.9019247, +2.4492764));
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin('#aladin-lite-div', {target: 'NGC 1367', fov: 360, samp: false, projection: 'AIT', fullScreen: true, showContextMenu: true});
|
||||
|
||||
A.catalogFromURL('https://raw.githubusercontent.com/VisIVOLab/SKA-Discovery-Service-Mockup/main/ObsCore/ObsCore_003.xml', {onClick: 'showTable'}, (catalog) => {
|
||||
A.catalogFromURL('https://raw.githubusercontent.com/VisIVOLab/SKA-Discovery-Service-Mockup/main/ObsCore/ObsCore_003.xml', {onClick: 'showTable', hoverColor: 'purple'}, (catalog) => {
|
||||
aladin.addCatalog(catalog)
|
||||
});
|
||||
|
||||
aladin.addCatalog(A.catalogFromVizieR("B/assocdata/obscore", "0 +0", 20, {limit: 1000}))
|
||||
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>
|
||||
45
examples/al-simbad-filter.html
Normal file
45
examples/al-simbad-filter.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.1.min.js"></script>
|
||||
|
||||
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
|
||||
|
||||
Show sources with proper motion greater than:
|
||||
<input id='slider' style='vertical-align:middle;width:60vw;' step='1' min='0' max='10' type='range' value='0'>
|
||||
<span id='pmVal' >0 mas/yr</span><br><br><div id='aladin-lite-div' style='width: 500px;height: 500px;'></div>
|
||||
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
let aladin;
|
||||
A.init.then(() => {
|
||||
var colorThreshold = 0;
|
||||
var slider = document.getElementById('slider');
|
||||
slider.oninput = function() {
|
||||
colorThreshold = this.value;
|
||||
$('#colorVal').html(colorThreshold);
|
||||
cat.reportChange();
|
||||
}
|
||||
var myFilterFunction = function(source) {
|
||||
const magB = parseFloat(source.data['B']);
|
||||
const magV = parseFloat(source.data['V']);
|
||||
if (isNaN(magB) || isNaN(magV) ) {
|
||||
return false;
|
||||
}
|
||||
const color = magB - magV;
|
||||
return color>colorThreshold;
|
||||
}
|
||||
|
||||
aladin = A.aladin('#aladin-lite-div', {target: 'M 81', fov: 0.5, survey: 'CDS/P/SDSS9/color'});
|
||||
var cat = A.catalogFromSimbad('M 81', 0.25, {onClick: 'showTable', verbosity: 3, filter: myFilterFunction});
|
||||
aladin.addCatalog(cat);
|
||||
});
|
||||
</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);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
let aladin = A.aladin('#aladin-lite-div', {survey: "CDS/P/DSS2/color", target: 'Sgr a*', fov: 0.5, showContextMenu: true});
|
||||
// This table contains a s_region column containing stcs expressed regions
|
||||
// that are automatically parsed
|
||||
aladin.addCatalog(A.catalogFromURL('https://aladin.cds.unistra.fr/AladinLite/doc/API/examples/data/alma-footprints.xml', {name: 'ALMA footprints', onClick: 'showTable'}));
|
||||
aladin.addCatalog(A.catalogFromURL('https://aladin.cds.unistra.fr/AladinLite/doc/API/examples/data/alma-footprints.xml', {name: 'ALMA footprints', onClick: 'showTable', hoverColor: 'lightgreen'}));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -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.0",
|
||||
"version": "3.3.3",
|
||||
"description": "An astronomical HiPS visualizer in the browser",
|
||||
"author": "Thomas Boch and Matthieu Baumann",
|
||||
"license": "GPL-3",
|
||||
@@ -33,7 +33,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"wasm": "wasm-pack build ./src/core --target web --release --out-name core -- --features webgl2 -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort ",
|
||||
"predeploy": "npm run build && rm -rf aladin-lite.tgz && npm pack",
|
||||
"predeploy": "npm run build && rm -rf aladin-lite*.tgz && npm pack",
|
||||
"deploy": "python3 deploy/deploy.py",
|
||||
"build": "npm run wasm && vite build && cp examples/index.html dist/index.html",
|
||||
"dev": "npm run build && vite",
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "aladin-lite"
|
||||
description = "Aladin Lite v3 introduces a new graphical engine written in Rust with the use of WebGL"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/cds-astro/aladin-lite"
|
||||
version = "3.3.0"
|
||||
version = "3.3.2"
|
||||
authors = [ "baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr",]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -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?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,8 @@ impl App {
|
||||
// Move the views of the different active surveys
|
||||
self.tile_fetcher.clear();
|
||||
// Loop over the surveys
|
||||
let raytracer = self.layers.get_raytracer();
|
||||
|
||||
for survey in self.layers.values_mut_hips() {
|
||||
if self.camera.get_texture_depth() == 0
|
||||
&& self
|
||||
@@ -290,7 +292,8 @@ impl App {
|
||||
let root_url = survey.get_config().get_root_url().to_string();
|
||||
let format = survey.get_config().get_format();
|
||||
|
||||
if let Some(tiles_iter) = survey.look_for_new_tiles(&mut self.camera) {
|
||||
if let Some(tiles_iter) = survey.look_for_new_tiles(&mut self.camera, &self.projection)
|
||||
{
|
||||
for tile_cell in tiles_iter.into_iter() {
|
||||
self.tile_fetcher.append(
|
||||
query::Tile::new(&tile_cell, creator_did.clone(), root_url.clone(), format),
|
||||
@@ -603,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();
|
||||
@@ -646,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;
|
||||
@@ -818,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();
|
||||
@@ -1494,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();
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::HEALPixCoverage;
|
||||
use std::ops::Range;
|
||||
|
||||
use al_api::cell::HEALPixCellProjeted;
|
||||
use al_core::log::console_log;
|
||||
|
||||
pub fn project(
|
||||
cell: HEALPixCellProjeted,
|
||||
|
||||
@@ -9,7 +9,9 @@ pub enum UserAction {
|
||||
use super::{fov::FieldOfView, view_hpx_cells::ViewHpxCells};
|
||||
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};
|
||||
@@ -210,12 +212,31 @@ impl CameraViewPort {
|
||||
|
||||
pub fn get_hpx_cells<'a>(
|
||||
&'a mut self,
|
||||
depth: u8,
|
||||
mut depth: u8,
|
||||
frame: CooSystem,
|
||||
) -> impl Iterator<Item = &'a HEALPixCell> {
|
||||
self.view_hpx_cells.get_cells(depth, frame)
|
||||
}
|
||||
|
||||
pub fn is_raytracing(&self, proj: &ProjectionType) -> bool {
|
||||
// Check whether the tile depth is 0 for square projection
|
||||
// definition domains i.e. Mercator
|
||||
if self.is_allsky() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check the projection
|
||||
match proj {
|
||||
ProjectionType::Tan(_) => self.aperture >= 100.0_f64.to_radians().to_angle(),
|
||||
ProjectionType::Mer(_) => self.aperture >= 200.0_f64.to_radians().to_angle(),
|
||||
ProjectionType::Stg(_) => self.aperture >= 200.0_f64.to_radians().to_angle(),
|
||||
ProjectionType::Sin(_) => false,
|
||||
ProjectionType::Ait(_) => false,
|
||||
ProjectionType::Mol(_) => false,
|
||||
ProjectionType::Zea(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn recompute_scissor(&self) {
|
||||
// Clear all the screen before updating the scissor
|
||||
//self.gl.scissor(0, 0, self.width as i32, self.height as i32);
|
||||
@@ -360,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> =
|
||||
|
||||
@@ -529,9 +529,10 @@ impl HiPS {
|
||||
pub fn look_for_new_tiles<'a>(
|
||||
&'a mut self,
|
||||
camera: &'a mut CameraViewPort,
|
||||
proj: &ProjectionType,
|
||||
) -> Option<impl Iterator<Item = HEALPixCell> + 'a> {
|
||||
// do not add tiles if the view is already at depth 0
|
||||
let depth_tile = (camera.get_texture_depth() + self.get_config().delta_depth())
|
||||
let mut depth_tile = (camera.get_texture_depth() + self.get_config().delta_depth())
|
||||
.min(self.get_config().get_max_depth_tile())
|
||||
.max(self.get_config().get_min_depth_tile());
|
||||
let dd = self.get_config().delta_depth();
|
||||
@@ -550,6 +551,12 @@ impl HiPS {
|
||||
//let depth_tile = depth_tile.max(min_bound_depth);
|
||||
let survey_frame = self.get_config().get_frame();
|
||||
let mut already_considered_tiles = HashSet::new();
|
||||
|
||||
// raytracer is rendering and the shader only renders HPX texture cells of depth 0
|
||||
if camera.is_raytracing(proj) {
|
||||
depth_tile = 0;
|
||||
}
|
||||
|
||||
let tile_cells_iter = camera
|
||||
.get_hpx_cells(depth_tile, survey_frame)
|
||||
//.flat_map(move |cell| {
|
||||
@@ -601,13 +608,8 @@ impl HiPS {
|
||||
self.textures.contains_tile(cell)
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
raytracer: &RayTracer,
|
||||
camera: &mut CameraViewPort,
|
||||
projection: &ProjectionType,
|
||||
) {
|
||||
let raytracing = raytracer.is_rendering(camera);
|
||||
pub fn update(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) {
|
||||
let raytracing = camera.is_raytracing(projection);
|
||||
|
||||
let vertices_recomputation_needed =
|
||||
!raytracing && (self.textures.reset_available_tiles() | camera.has_moved());
|
||||
@@ -1011,6 +1013,7 @@ impl HiPS {
|
||||
camera: &CameraViewPort,
|
||||
raytracer: &RayTracer,
|
||||
cfg: &ImageMetadata,
|
||||
proj: &ProjectionType,
|
||||
) -> Result<(), JsValue> {
|
||||
// Get the coo system transformation matrix
|
||||
let selected_frame = camera.get_coo_system();
|
||||
@@ -1022,7 +1025,7 @@ impl HiPS {
|
||||
let w2v = c * (*camera.get_w2m());
|
||||
let v2w = w2v.transpose();
|
||||
|
||||
let raytracing = raytracer.is_rendering(camera);
|
||||
let raytracing = camera.is_raytracing(proj);
|
||||
let config = self.get_config();
|
||||
|
||||
self.gl.enable(WebGl2RenderingContext::BLEND);
|
||||
|
||||
@@ -239,11 +239,4 @@ impl RayTracer {
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_rendering(&self, camera: &CameraViewPort) -> bool {
|
||||
// Check whether the tile depth is 0 for square projection
|
||||
// definition domains i.e. Mercator
|
||||
let depth = camera.get_texture_depth();
|
||||
camera.is_allsky() || depth == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,10 @@ impl Layers {
|
||||
self.background_color = color;
|
||||
}
|
||||
|
||||
pub fn get_raytracer(&self) -> &RayTracer {
|
||||
&self.raytracer
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
camera: &mut CameraViewPort,
|
||||
@@ -222,7 +226,7 @@ impl Layers {
|
||||
projection: &ProjectionType,
|
||||
) -> Result<(), JsValue> {
|
||||
let raytracer = &self.raytracer;
|
||||
let raytracing = raytracer.is_rendering(camera);
|
||||
let raytracing = camera.is_raytracing(projection);
|
||||
|
||||
// Check whether a survey to plot is allsky
|
||||
// if neither are, we draw a font
|
||||
@@ -292,10 +296,10 @@ impl Layers {
|
||||
// 1. Update the survey if necessary
|
||||
let id = self.ids.get(layer).expect("Url should be found");
|
||||
if let Some(survey) = self.surveys.get_mut(id) {
|
||||
survey.update(&self.raytracer, camera, projection);
|
||||
survey.update(camera, projection);
|
||||
|
||||
// 2. Draw it if its opacity is not null
|
||||
survey.draw(shaders, colormaps, camera, raytracer, draw_opt)?;
|
||||
survey.draw(shaders, colormaps, camera, raytracer, draw_opt, projection)?;
|
||||
} else if let Some(image) = self.images.get_mut(id) {
|
||||
image.update(camera, projection)?;
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
body { overscroll-behavior: contain; }
|
||||
|
||||
.aladin-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
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;
|
||||
@@ -15,12 +11,6 @@ body { overscroll-behavior: contain; }
|
||||
}
|
||||
|
||||
.aladin-imageCanvas {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.aladin-gridCanvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
@@ -57,18 +47,6 @@ body { overscroll-behavior: contain; }
|
||||
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;
|
||||
@@ -115,6 +93,7 @@ body { overscroll-behavior: contain; }
|
||||
|
||||
.aladin-measurement-div.aladin-dark-theme table thead {
|
||||
background-color: #000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.aladin-measurement-div table td.aladin-href-td-container a:hover {
|
||||
@@ -412,6 +391,9 @@ canvas {
|
||||
.aladin-measurement-div.aladin-dark-theme {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.aladin-measurement-div.aladin-dark-theme table {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -447,7 +429,7 @@ canvas {
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme.search {
|
||||
width: 14rem;
|
||||
width: 15rem;
|
||||
text-shadow: 0px 0px 2px #000;
|
||||
}
|
||||
|
||||
@@ -462,6 +444,7 @@ canvas {
|
||||
background-image:none;
|
||||
text-indent: 0rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
|
||||
.aladin-input-text.search.aladin-unknownObject {
|
||||
@@ -751,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 {
|
||||
@@ -837,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 {
|
||||
@@ -866,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;
|
||||
@@ -1117,10 +1107,23 @@ canvas {
|
||||
height: 1.7rem;
|
||||
}
|
||||
|
||||
.aladin-input-text.aladin-dark-theme.search.aladin-HiPS-search {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.aladin-stack-box {
|
||||
width: 17rem;
|
||||
}
|
||||
|
||||
.aladin-stack-box .content > * {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.aladin-location {
|
||||
position: absolute;
|
||||
top: 0.2rem;
|
||||
left: 7.15rem;
|
||||
left: 6.9rem;
|
||||
font-family: monospace;
|
||||
|
||||
color: white;
|
||||
@@ -1135,6 +1138,8 @@ canvas {
|
||||
bottom: 0.2rem;
|
||||
left: 0.2rem;
|
||||
|
||||
background-color: red;
|
||||
|
||||
font-family: monospace;
|
||||
|
||||
font-size: 1rem;
|
||||
@@ -1142,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;
|
||||
@@ -1168,6 +1183,9 @@ canvas {
|
||||
max-width: calc(100% - 0.4rem);
|
||||
line-height: 1rem;
|
||||
}
|
||||
.aladin-measurement-div.aladin-dark-theme {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.aladin-share-control {
|
||||
position: absolute;
|
||||
|
||||
243
src/js/A.js
243
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";
|
||||
@@ -44,6 +45,7 @@ import { Aladin } from "./Aladin.js";
|
||||
import { ActionButton } from "./gui/Widgets/ActionButton.js";
|
||||
import { Box } from "./gui/Widgets/Box.js";
|
||||
import { AladinUtils } from "./AladinUtils.js";
|
||||
import { Sesame } from "./Sesame.js";
|
||||
|
||||
// Wasm top level import
|
||||
import init, * as module from './../core/pkg';
|
||||
@@ -145,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
|
||||
@@ -164,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);
|
||||
@@ -182,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;
|
||||
@@ -210,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);
|
||||
@@ -226,7 +230,7 @@ A.polyline = function (raDecArray, options) {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a circle object
|
||||
* Creates a circle shape
|
||||
*
|
||||
* @function
|
||||
* @memberof A
|
||||
@@ -236,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
|
||||
@@ -255,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.
|
||||
*
|
||||
@@ -316,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);
|
||||
@@ -344,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);
|
||||
@@ -434,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);
|
||||
}
|
||||
@@ -502,9 +627,9 @@ A.catalogFromURL = function (url, options, successCallback, errorCallback, usePr
|
||||
* @param {number} target.dec - Declination in degrees of the cone's center
|
||||
* @param {number} radius - Radius of the cone in degrees
|
||||
* @param {Object|CatalogOptions} [options] - Additional configuration options for SIMBAD cone search. See the {@link https://simbad.cds.unistra.fr/cone/help/#/ConeSearch/get_ SIMBAD cone search} parameters.
|
||||
* @param {Object} [options.limit] - The max number of sources to return
|
||||
* @param {Object} [options.orderBy] - Order the result by specific
|
||||
*
|
||||
* @param {number} [options.limit] - The max number of sources to return
|
||||
* @param {string} [options.orderBy='nb_ref'] - Order the result by specific ref number
|
||||
* @param {number} [options.verbosity=2] - Verbosity, put 3 if you want all the column
|
||||
* @param {function} [successCallback] - The callback function to execute on successful catalog creation.
|
||||
* @param {function} [errorCallback] - The callback function to execute on error during catalog creation.
|
||||
* @returns {Catalog} A new instance of the Catalog class created from the SIMBAD cone search.
|
||||
@@ -519,8 +644,8 @@ A.catalogFromSimbad = function (target, radius, options, successCallback, errorC
|
||||
if (!('name' in options)) {
|
||||
options['name'] = 'Simbad';
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let cat = A.catalog(options);
|
||||
new Promise((resolve, reject) => {
|
||||
let coo;
|
||||
if (target && (typeof target === "object")) {
|
||||
if ('ra' in target && 'dec' in target) {
|
||||
@@ -556,8 +681,44 @@ A.catalogFromSimbad = function (target, radius, options, successCallback, errorC
|
||||
}
|
||||
}).then((coo) => {
|
||||
const url = URLBuilder.buildSimbadCSURL(coo.lon, coo.lat, radius, options)
|
||||
return A.catalogFromURL(url, options, successCallback, errorCallback, false);
|
||||
const processVOTable = function (table) {
|
||||
let {sources, fields} = table;
|
||||
cat.setFields(fields);
|
||||
|
||||
if (cat.type === 'ObsCore') {
|
||||
// The fields corresponds to obscore ones
|
||||
// Set the name of the catalog to be ObsCore:<catalog name>
|
||||
cat.name = "ObsCore:" + url;
|
||||
}
|
||||
|
||||
cat.addSources(sources);
|
||||
|
||||
if (successCallback) {
|
||||
successCallback(cat);
|
||||
}
|
||||
|
||||
if (sources.length === 0) {
|
||||
console.warn(cat.name + ' has no sources!')
|
||||
}
|
||||
|
||||
// Even if the votable is not a proper ObsCore one, try to see if specific columns are given
|
||||
// e.g. access_format and access_url
|
||||
//ObsCore.handleActions(catalog);
|
||||
};
|
||||
|
||||
|
||||
Catalog.parseVOTable(
|
||||
url,
|
||||
processVOTable,
|
||||
errorCallback,
|
||||
cat.maxNbSources,
|
||||
false,
|
||||
cat.raField, cat.decField
|
||||
);
|
||||
|
||||
})
|
||||
|
||||
return cat;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
281
src/js/Aladin.js
281
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,6 +140,84 @@ 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
|
||||
* @description Options for configuring the Aladin Lite instance.
|
||||
*
|
||||
* @property {number} x - x coordinate of the center's circle in pixels
|
||||
* @property {number} y - y coordinate of the center's circle in pixels
|
||||
* @property {number} r - radius of the circle in pixels
|
||||
* @property {function} contains - function taking a {x, y} object telling if the vertex is contained or not
|
||||
* @property {function} bbox - returns the bbox of the selection in pixels
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RectSelection
|
||||
* @description Options for configuring the Aladin Lite instance.
|
||||
*
|
||||
* @property {number} x - top left x coordinate of the rectangle in pixels
|
||||
* @property {number} y - top left y coordinate of the rectangle in pixels
|
||||
* @property {number} w - width of the selection in pixels
|
||||
* @property {number} h - height of the selection in pixels
|
||||
* @property {function} contains - function taking a {x, y} object telling if the vertex is contained in the selection or not
|
||||
* @property {function} bbox - returns the bbox of the selection in pixels
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PolygonSelection
|
||||
* @description Options for configuring the Aladin Lite instance.
|
||||
*
|
||||
* @property {Object[]} vertices - vertices of the polygon selection in pixels. Each vertex has a x and y key in pixels.
|
||||
* @property {function} contains - function taking a {x, y} object telling if the vertex is contained in the selection or not
|
||||
* @property {function} bbox - returns the bbox of the selection in pixels
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -184,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) {
|
||||
@@ -196,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];
|
||||
@@ -239,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;
|
||||
@@ -246,6 +333,7 @@ export let Aladin = (function () {
|
||||
if (options && options.showCooGrid) {
|
||||
gridOptions.enabled = true;
|
||||
}
|
||||
|
||||
this.setCooGrid(gridOptions);
|
||||
|
||||
this.gotoObject(options.target, undefined);
|
||||
@@ -300,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
|
||||
@@ -334,6 +500,10 @@ export let Aladin = (function () {
|
||||
this.samp = new SAMPConnector(this);
|
||||
}
|
||||
|
||||
if (options.inertia !== undefined) {
|
||||
this.wasm.setInertia(options.inertia)
|
||||
}
|
||||
|
||||
this._setupUI(options);
|
||||
};
|
||||
|
||||
@@ -373,6 +543,7 @@ export let Aladin = (function () {
|
||||
if (!options.showLayersControl) {
|
||||
stack._hide();
|
||||
}
|
||||
|
||||
// Add the simbad pointer control
|
||||
if (!options.showSimbadPointerControl) {
|
||||
simbad._hide();
|
||||
@@ -404,6 +575,10 @@ export let Aladin = (function () {
|
||||
this.addUI(new FullScreenActionButton(self))
|
||||
}
|
||||
|
||||
if (options.expandLayersControl) {
|
||||
stack.toggle();
|
||||
}
|
||||
|
||||
this._applyMediaQueriesUI();
|
||||
}
|
||||
|
||||
@@ -455,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,
|
||||
@@ -492,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
|
||||
@@ -618,6 +797,8 @@ export let Aladin = (function () {
|
||||
this.view.setZoom(FoV);
|
||||
};
|
||||
|
||||
Aladin.prototype.setFov = Aladin.prototype.setFoV;
|
||||
|
||||
// @API
|
||||
// (experimental) try to adjust the FoV to the given object name. Does nothing if object is not known from Simbad
|
||||
Aladin.prototype.adjustFovForObject = function (objectName) {
|
||||
@@ -663,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;
|
||||
}
|
||||
@@ -809,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());
|
||||
@@ -872,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) {
|
||||
@@ -894,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']) {
|
||||
@@ -928,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)
|
||||
})
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -1095,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);
|
||||
@@ -1194,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;
|
||||
}
|
||||
|
||||
@@ -1523,8 +1722,12 @@ export let Aladin = (function () {
|
||||
* Enters selection mode
|
||||
*
|
||||
* @memberof Aladin
|
||||
* @param {string} mode=rect - The mode of selection, can be either, 'rect', 'poly', or 'circle'
|
||||
* @param {function} callback - A function called once the selection has been done
|
||||
* @param {string} [mode='rect'] - The mode of selection, can be either, 'rect', 'poly', or 'circle'
|
||||
* @param {function} [callback] - A function called once the selection has been done
|
||||
* The callback accepts one parameter depending of the mode used: <br/>
|
||||
* - If mode='circle' that parameter is of type {@link CircleSelection} <br/>
|
||||
* - If mode='rect' that parameter is of type {@link RectSelection} <br/>
|
||||
* - If mode='poly' that parameter is of type {@link PolygonSelection}
|
||||
*
|
||||
* @example
|
||||
* // Creates and add a MOC from the user polygonal selection
|
||||
@@ -1611,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) {
|
||||
@@ -1899,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);
|
||||
}
|
||||
}
|
||||
@@ -2055,7 +2258,8 @@ export let Aladin = (function () {
|
||||
* @param {Function} successCallback - The callback function to be executed on a successful display.
|
||||
* The callback gives the ra, dec, and fov of the image;
|
||||
* @param {Function} errorCallback - The callback function to be executed if an error occurs during display.
|
||||
*
|
||||
* @param {string} [layer="base"] - The name of the layer. If not specified, it will be replace the base layer.
|
||||
*
|
||||
* @example
|
||||
* aladin.displayJPG(
|
||||
* // the JPG to transform to HiPS
|
||||
@@ -2097,6 +2301,7 @@ export let Aladin = (function () {
|
||||
* @param {Function} successCallback - The callback function to be executed on a successful display.
|
||||
* The callback gives the ra, dec, and fov of the image;
|
||||
* @param {Function} errorCallback - The callback function to be executed if an error occurs during display.
|
||||
* @param {string} [layer="overlay"] - The name of the layer. If not specified, it will add a new overlay layer on top of the base.
|
||||
*
|
||||
* @example
|
||||
* aladin.displayJPG(
|
||||
@@ -2111,7 +2316,7 @@ export let Aladin = (function () {
|
||||
* })
|
||||
*);
|
||||
*/
|
||||
Aladin.prototype.displayJPG = function (url, options, successCallback, errorCallback) {
|
||||
Aladin.prototype.displayJPG = function (url, options, successCallback, errorCallback, layer = "overlay") {
|
||||
options = options || {};
|
||||
options.color = true;
|
||||
options.label = options.label || "JPG/PNG image";
|
||||
@@ -2161,7 +2366,7 @@ export let Aladin = (function () {
|
||||
var meta = response.data.meta;
|
||||
|
||||
const survey = self.createImageSurvey(response.data.url, label, response.data.url);
|
||||
self.setOverlayImageLayer(survey, "overlay");
|
||||
self.setOverlayImageLayer(survey, layer);
|
||||
|
||||
var transparency = (options && options.transparency) || 1.0;
|
||||
survey.setOpacity(transparency);
|
||||
|
||||
@@ -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,13 +59,15 @@ 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.
|
||||
* @param {string} [options.raField] - The ID or name of the field holding Right Ascension (RA).
|
||||
* @param {string} [options.decField] - The ID or name of the field holding Declination (dec).
|
||||
* @param {function} [options.filter] - The filtering function for sources.
|
||||
* @param {string} [options.selectionColor] - The color to apply to selected sources in the catalog.
|
||||
* @param {string} [options.hoverColor] - The color to apply to sources in the catalog when they are hovered.
|
||||
* @param {boolean} [options.displayLabel=false] - Whether to display labels for sources.
|
||||
* @param {string} [options.labelColumn] - The name of the column to be used for the label.
|
||||
* @param {string} [options.labelColor] - The color of the source labels.
|
||||
@@ -81,6 +87,8 @@ export let Catalog = (function() {
|
||||
* raField: "ra",
|
||||
* decField: "dec",
|
||||
* filter: (source) => source.mag < 15,
|
||||
* selectionColor: "#00ff00",
|
||||
* hoverColor: "#ff00ff",
|
||||
* displayLabel: true,
|
||||
* labelColor: "#00ff00",
|
||||
* labelFont: "12px Arial"
|
||||
@@ -107,7 +115,8 @@ export let Catalog = (function() {
|
||||
|
||||
// allows for filtering of sources
|
||||
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
|
||||
|
||||
this.selectionColor = options.selectionColor || '#00ff00';
|
||||
this.hoverColor = options.hoverColor || this.color;
|
||||
this.displayLabel = options.displayLabel || false;
|
||||
this.labelColor = options.labelColor || this.color;
|
||||
this.labelFont = options.labelFont || '10px sans-serif';
|
||||
@@ -118,8 +127,6 @@ export let Catalog = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
this.selectionColor = '#00ff00';
|
||||
|
||||
this.showFieldCallback = {}; // callbacks when the user clicks on a cell in the measurement table associated
|
||||
this.fields = undefined;
|
||||
this.uuid = Utils.uuidv4();
|
||||
@@ -285,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;
|
||||
@@ -315,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) => {
|
||||
@@ -358,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();
|
||||
|
||||
@@ -400,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;
|
||||
@@ -428,10 +432,9 @@ export let Catalog = (function() {
|
||||
|
||||
if (successCallback) {
|
||||
successCallback({
|
||||
sources: sources,
|
||||
footprints: footprints,
|
||||
fields: fields,
|
||||
type: type
|
||||
sources,
|
||||
//footprints,
|
||||
fields,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -450,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;
|
||||
}
|
||||
|
||||
@@ -462,7 +470,7 @@ export let Catalog = (function() {
|
||||
|
||||
this.cacheCanvas = Catalog.createShape(this.shape, this.color, this.sourceSize);
|
||||
this.cacheSelectCanvas = Catalog.createShape(this.shape, this.selectionColor, this.selectSize);
|
||||
this.cacheHoverCanvas = Catalog.createShape(this.shape, this.hoverColor, this.sourceSize);
|
||||
this.cacheHoverCanvas = Catalog.createShape(this.shape, this.hoverColor, this.selectSize);
|
||||
|
||||
this.reportChange();
|
||||
};
|
||||
@@ -476,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 = [];
|
||||
@@ -497,19 +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.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;
|
||||
@@ -653,6 +694,8 @@ export let Catalog = (function() {
|
||||
this.ra.splice(idx, 1);
|
||||
this.dec.splice(idx, 1);
|
||||
|
||||
this.recomputeFootprints = true;
|
||||
|
||||
this.reportChange();
|
||||
};
|
||||
|
||||
@@ -664,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();
|
||||
@@ -687,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;
|
||||
@@ -712,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) {
|
||||
@@ -725,8 +767,12 @@ 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) {
|
||||
@@ -735,6 +781,9 @@ export let Catalog = (function() {
|
||||
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);
|
||||
}
|
||||
@@ -764,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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
116
src/js/Circle.js
116
src/js/Circle.js
@@ -29,33 +29,49 @@
|
||||
*****************************************************************************/
|
||||
|
||||
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;
|
||||
this.fillColor = options['fillColor'] || undefined;
|
||||
this.lineWidth = options["lineWidth"] || 2;
|
||||
this.selectionColor = options["selectionColor"] || '#00ff00';
|
||||
this.hoverColor = options["hoverColor"] || undefined;
|
||||
|
||||
// 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;
|
||||
this.isSelected = false;
|
||||
this.selectionColor = '#00ff00';
|
||||
this.isHovered = false;
|
||||
};
|
||||
|
||||
Circle.prototype.setColor = function(color) {
|
||||
if (this.color == color) {
|
||||
if (!color || this.color == color) {
|
||||
return;
|
||||
}
|
||||
this.color = color;
|
||||
@@ -65,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;
|
||||
@@ -74,6 +90,16 @@ export let Circle = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Circle.prototype.setHoverColor = function(color) {
|
||||
if (!color || this.hoverColor == color) {
|
||||
return;
|
||||
}
|
||||
this.hoverColor = color;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Circle.prototype.setLineWidth = function(lineWidth) {
|
||||
if (this.lineWidth == lineWidth) {
|
||||
return;
|
||||
@@ -132,6 +158,26 @@ export let Circle = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Circle.prototype.hover = function() {
|
||||
if (this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = true;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
}
|
||||
|
||||
Circle.prototype.unhover = function() {
|
||||
if (! this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = false;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
}
|
||||
|
||||
Circle.prototype.isFootprint = function() {
|
||||
return true;
|
||||
}
|
||||
@@ -153,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],
|
||||
@@ -171,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
|
||||
|
||||
@@ -224,8 +255,9 @@ export let Circle = (function() {
|
||||
} else {
|
||||
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else if (this.isHovered) {
|
||||
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
|
||||
} else {
|
||||
ctx.strokeStyle = baseColor;
|
||||
}
|
||||
|
||||
@@ -239,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,35 +29,68 @@
|
||||
*****************************************************************************/
|
||||
|
||||
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;
|
||||
this.fillColor = options['fillColor'] || undefined;
|
||||
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;
|
||||
this.isSelected = false;
|
||||
|
||||
this.selectionColor = '#00ff00';
|
||||
this.isHovered = false;
|
||||
};
|
||||
|
||||
Ellipse.prototype.setColor = function(color) {
|
||||
if (this.color == color) {
|
||||
if (!color || this.color == color) {
|
||||
return;
|
||||
}
|
||||
this.color = color;
|
||||
@@ -67,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;
|
||||
@@ -76,6 +109,16 @@ export let Ellipse = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Ellipse.prototype.setHoverColor = function(color) {
|
||||
if (!color || this.hoverColor == color) {
|
||||
return;
|
||||
}
|
||||
this.hoverColor = color;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Ellipse.prototype.setLineWidth = function(lineWidth) {
|
||||
if (this.lineWidth == lineWidth) {
|
||||
return;
|
||||
@@ -135,6 +178,25 @@ export let Ellipse = (function() {
|
||||
};
|
||||
|
||||
|
||||
Ellipse.prototype.hover = function() {
|
||||
if (this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = true;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
}
|
||||
|
||||
Ellipse.prototype.unhover = function() {
|
||||
if (! this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = false;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
}
|
||||
|
||||
Ellipse.prototype.setCenter = function(centerRaDec) {
|
||||
this.centerRaDec = centerRaDec;
|
||||
@@ -156,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();
|
||||
@@ -170,69 +232,36 @@ 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) {
|
||||
// the center goes out of the projection
|
||||
// we do not draw it
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!circlePtXyViewRa || !circlePtXyViewDec) {
|
||||
// the circle border goes out of the projection
|
||||
// we do not draw it
|
||||
return;
|
||||
}
|
||||
|
||||
var dxRa = circlePtXyViewRa[0] - centerXyview[0];
|
||||
var dyRa = circlePtXyViewRa[1] - centerXyview[1];
|
||||
var radiusInPixX = 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);
|
||||
|
||||
// Ellipse crossing the projection
|
||||
if ((dxRa*dyDec - dxDec*dyRa) <= 0.0) {
|
||||
// We do not draw it
|
||||
return;
|
||||
}
|
||||
noStroke = noStroke===true || false;
|
||||
|
||||
// TODO : check each 4 point until show
|
||||
var baseColor = this.color;
|
||||
if (! baseColor && this.overlay) {
|
||||
baseColor = this.overlay.color;
|
||||
}
|
||||
if (! baseColor) {
|
||||
baseColor = '#ff0000';
|
||||
}
|
||||
|
||||
if (this.isSelected) {
|
||||
if(this.selectionColor) {
|
||||
ctx.strokeStyle = this.selectionColor;
|
||||
} else {
|
||||
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
|
||||
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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ctx.strokeStyle = baseColor;
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
// 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]);
|
||||
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]];
|
||||
@@ -250,9 +279,62 @@ export let Ellipse = (function() {
|
||||
|
||||
// 5. Get the correct ellipse angle
|
||||
let theta = -this.rotation + westToNorthAngle;
|
||||
//let ct = Math.cos(theta);
|
||||
//let st = Math.sin(theta);
|
||||
|
||||
/*let circlePtXyViewRa = view.aladin.world2pix(view.viewCenter.lon + 1.0, view.viewCenter.lat);
|
||||
let circlePtXyViewDec = view.aladin.world2pix(view.viewCenter.lon, view.viewCenter.lat + 1.0);
|
||||
|
||||
if (!circlePtXyViewRa || !circlePtXyViewDec) {
|
||||
// the circle border goes out of the projection
|
||||
// we do not draw it
|
||||
return;
|
||||
}
|
||||
|
||||
var dxRa = circlePtXyViewRa[0] - centerXyview[0];
|
||||
var dyRa = circlePtXyViewRa[1] - centerXyview[1];
|
||||
var dRa = Math.sqrt(dxRa*dxRa + dyRa*dyRa);
|
||||
|
||||
var dxDec = circlePtXyViewDec[0] - centerXyview[0];
|
||||
var dyDec = circlePtXyViewDec[1] - centerXyview[1];
|
||||
var dDec = Math.sqrt(dxDec*dxDec + dyDec*dyDec);*/
|
||||
|
||||
//var radiusInPixX = Math.abs(this.a * ct * dRa) + Math.abs(this.a * st * dDec);
|
||||
//var radiusInPixY = Math.abs(this.b * st * dRa) + Math.abs(this.b * ct * dDec);
|
||||
|
||||
// Ellipse crossing the projection
|
||||
/*if ((dxRa*dyDec - dxDec*dyRa) <= 0.0) {
|
||||
// We do not draw it
|
||||
return;
|
||||
}*/
|
||||
noStroke = noStroke===true || false;
|
||||
|
||||
// TODO : check each 4 point until show
|
||||
var baseColor = this.color;
|
||||
if (! baseColor && this.overlay) {
|
||||
baseColor = this.overlay.color;
|
||||
}
|
||||
if (! baseColor) {
|
||||
baseColor = '#ff0000';
|
||||
}
|
||||
|
||||
if (this.isSelected) {
|
||||
if(this.selectionColor) {
|
||||
ctx.strokeStyle = this.selectionColor;
|
||||
} else {
|
||||
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
|
||||
}
|
||||
} else if (this.isHovered) {
|
||||
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
|
||||
} else {
|
||||
ctx.strokeStyle = baseColor;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -260,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,9 +42,14 @@ 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;
|
||||
|
||||
this.overlay = null;
|
||||
};
|
||||
@@ -53,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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,30 +96,81 @@ export let Footprint= (function() {
|
||||
this.shapes.forEach((shape) => shape.deselect())
|
||||
};
|
||||
|
||||
Footprint.prototype.hover = function() {
|
||||
if (this.isHovered) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHovered = true;
|
||||
this.shapes.forEach((shape) => shape.hover())
|
||||
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
return;
|
||||
}
|
||||
|
||||
let catalog = this.getCatalog();
|
||||
if (catalog) {
|
||||
catalog.view && catalog.view.requestRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
Footprint.prototype.unhover = function() {
|
||||
if (!this.isHovered) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHovered = false;
|
||||
this.shapes.forEach((shape) => shape.unhover())
|
||||
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
|
||||
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))
|
||||
};
|
||||
|
||||
Footprint.prototype.isFootprint = function() {
|
||||
return true;
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -127,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;
|
||||
};
|
||||
@@ -150,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 {
|
||||
|
||||
@@ -209,8 +209,6 @@ export let ImageFITS = (function () {
|
||||
|
||||
return self;
|
||||
}).catch((e) => {
|
||||
window.alert(e + ". See the javascript console for more logging details.")
|
||||
|
||||
if (self.errorCallback) {
|
||||
self.errorCallback()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -174,6 +173,13 @@ export let ImageSurvey = (function () {
|
||||
ImageSurvey.prototype.setView = function(view) {
|
||||
let self = this;
|
||||
|
||||
// do not allow to call setView multiple times otherwise
|
||||
// the querying to the properties and the search to the best
|
||||
// HiPS node will be done again for the same imageHiPS
|
||||
if (self.view) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.view = view;
|
||||
|
||||
let isMOCServerToBeQueried = true;
|
||||
@@ -186,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;
|
||||
@@ -231,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
|
||||
@@ -348,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
|
||||
@@ -362,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,
|
||||
@@ -379,6 +385,7 @@ export let ImageSurvey = (function () {
|
||||
maxOrder: self.maxOrder,
|
||||
tileSize: self.tileSize,
|
||||
imgFormat: self.imgFormat,
|
||||
...colorOpt
|
||||
}
|
||||
|
||||
if (self.numBitsPerPixel) {
|
||||
@@ -392,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.
|
||||
@@ -462,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
|
||||
@@ -655,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);
|
||||
@@ -690,7 +707,7 @@ export let ImageSurvey = (function () {
|
||||
},
|
||||
});
|
||||
|
||||
this.added = true;
|
||||
//this.added = true;
|
||||
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
@@ -734,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",
|
||||
@@ -884,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",
|
||||
@@ -898,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;
|
||||
@@ -83,12 +110,10 @@ export let Polyline= (function() {
|
||||
this.fillColor = options['fillColor'] || undefined;
|
||||
this.opacity = options['opacity'] || undefined;
|
||||
this.lineWidth = options["lineWidth"] || undefined;
|
||||
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();
|
||||
@@ -98,8 +123,7 @@ export let Polyline= (function() {
|
||||
|
||||
this.isShowing = true;
|
||||
this.isSelected = false;
|
||||
|
||||
this.selectionColor = '#00ff00';
|
||||
this.isHovered = false;
|
||||
};
|
||||
|
||||
Polyline.prototype.setOverlay = function(overlay) {
|
||||
@@ -146,6 +170,26 @@ export let Polyline= (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.hover = function() {
|
||||
if (this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = true;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.unhover = function() {
|
||||
if (! this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = false;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.getLineWidth = function() {
|
||||
return this.lineWidth;
|
||||
};
|
||||
@@ -162,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();
|
||||
@@ -172,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;
|
||||
@@ -181,6 +226,16 @@ export let Polyline= (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.setHoverColor = function(color) {
|
||||
if (!color || this.hoverColor == color) {
|
||||
return;
|
||||
}
|
||||
this.hoverColor = color;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.isFootprint = function() {
|
||||
// The polyline is a footprint if it describes a polygon (i.e. a closed polyline)
|
||||
return this.closed;
|
||||
@@ -188,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;
|
||||
@@ -206,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) {
|
||||
@@ -215,9 +270,10 @@ export let Polyline= (function() {
|
||||
} else {
|
||||
ctx.strokeStyle = Overlay.increaseBrightness(baseColor, 50);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ctx.strokeStyle= baseColor;
|
||||
} else if (this.isHovered) {
|
||||
ctx.strokeStyle = this.hoverColor || Overlay.increaseBrightness(baseColor, 25);
|
||||
} else {
|
||||
ctx.strokeStyle = baseColor;
|
||||
}
|
||||
|
||||
// 1. project the vertices into the screen
|
||||
@@ -231,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]});
|
||||
@@ -244,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;
|
||||
@@ -254,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);
|
||||
|
||||
@@ -314,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;
|
||||
@@ -383,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) {
|
||||
@@ -397,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;
|
||||
}
|
||||
@@ -409,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;
|
||||
@@ -418,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;
|
||||
@@ -433,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() {
|
||||
@@ -65,6 +64,8 @@ export let ProgressiveCat = (function() {
|
||||
this.sourceSize = options.sourceSize || 6;
|
||||
this.selectSize = this.sourceSize + 2;
|
||||
this.selectionColor = '#00ff00'; // TODO: to be merged with Catalog
|
||||
this.hoverColor = options.hoverColor || this.color;
|
||||
|
||||
|
||||
// allows for filtering of sources
|
||||
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
|
||||
@@ -72,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;
|
||||
@@ -217,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 = {
|
||||
@@ -270,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;
|
||||
@@ -287,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;
|
||||
@@ -317,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();
|
||||
@@ -337,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();
|
||||
@@ -354,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;
|
||||
}
|
||||
@@ -364,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);
|
||||
@@ -443,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) {
|
||||
@@ -472,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();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -499,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;
|
||||
@@ -557,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]);
|
||||
@@ -576,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 {
|
||||
|
||||
@@ -46,6 +46,7 @@ export let Source = (function() {
|
||||
|
||||
this.isShowing = true;
|
||||
this.isSelected = false;
|
||||
this.isHovered = false;
|
||||
};
|
||||
|
||||
Source.prototype.setCatalog = function(catalog) {
|
||||
@@ -96,6 +97,26 @@ export let Source = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Source.prototype.hover = function() {
|
||||
if (this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = true;
|
||||
if (this.catalog) {
|
||||
this.catalog.reportChange();
|
||||
}
|
||||
}
|
||||
|
||||
Source.prototype.unhover = function() {
|
||||
if (! this.isHovered) {
|
||||
return;
|
||||
}
|
||||
this.isHovered = false;
|
||||
if (this.catalog) {
|
||||
this.catalog.reportChange();
|
||||
}
|
||||
}
|
||||
|
||||
// function called when a source is clicked. Called by the View object
|
||||
Source.prototype.actionClicked = function(obj) {
|
||||
if (this.catalog && this.catalog.onClick) {
|
||||
|
||||
@@ -40,6 +40,10 @@ export let URLBuilder = (function() {
|
||||
url += '&MAXREC=' + options.limit;
|
||||
}
|
||||
|
||||
if (options && options.verbosity) {
|
||||
url += '&VERB=' + options.verbosity;
|
||||
}
|
||||
|
||||
const orderBy = options && options.orderBy || 'nb_ref';
|
||||
url += '&ORDER_BY=' + orderBy;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
377
src/js/View.js
377
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;
|
||||
@@ -234,8 +233,9 @@ export let View = (function () {
|
||||
|
||||
let doit;
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
clearTimeout(doit);
|
||||
doit = setTimeout(resizeLayout, 100);
|
||||
//clearTimeout(doit);
|
||||
//doit = setTimeout(resizeLayout, 100);
|
||||
resizeLayout();
|
||||
});
|
||||
|
||||
self.resizeObserver.observe(this.aladinDiv)
|
||||
@@ -315,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) {
|
||||
@@ -338,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');
|
||||
};
|
||||
|
||||
@@ -350,9 +350,9 @@ export let View = (function () {
|
||||
this.aladinDiv.style.setProperty('line-height', 0);
|
||||
Utils.cssScale = undefined;
|
||||
|
||||
var computedWidth = parseFloat(window.getComputedStyle(this.aladinDiv).width) || 1.0;
|
||||
var computedHeight = parseFloat(window.getComputedStyle(this.aladinDiv).height) || 1.0;
|
||||
|
||||
var computedWidth = Math.floor(parseFloat(this.aladinDiv.getBoundingClientRect().width)) || 1.0;
|
||||
var computedHeight = Math.floor(parseFloat(this.aladinDiv.getBoundingClientRect().height)) || 1.0;
|
||||
|
||||
this.width = Math.max(computedWidth, 1);
|
||||
this.height = Math.max(computedHeight, 1); // this prevents many problems when div size is equal to 0
|
||||
|
||||
@@ -368,21 +368,21 @@ 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";
|
||||
|
||||
this.wasm.resize(this.width, this.height);
|
||||
|
||||
|
||||
pixelateCanvasContext(this.imageCtx, this.aladin.options.pixelateCanvas);
|
||||
|
||||
// change logo
|
||||
@@ -469,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');
|
||||
|
||||
@@ -489,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;
|
||||
};
|
||||
|
||||
@@ -508,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;
|
||||
@@ -666,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));
|
||||
@@ -991,22 +989,32 @@ export let View = (function () {
|
||||
}
|
||||
}
|
||||
|
||||
if (lastHoveredObject && o != lastHoveredObject) {
|
||||
lastHoveredObject.unhover();
|
||||
|
||||
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
||||
|
||||
if (typeof objHoveredStopFunction === 'function') {
|
||||
objHoveredStopFunction(lastHoveredObject, xymouse);
|
||||
}
|
||||
}
|
||||
|
||||
if (o != lastHoveredObject) {
|
||||
o.hover();
|
||||
}
|
||||
lastHoveredObject = o;
|
||||
} else {
|
||||
view.setCursor('default');
|
||||
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
||||
if (lastHoveredObject) {
|
||||
// Redraw the scene if the lastHoveredObject is a footprint (e.g. circle or polygon)
|
||||
//if (lastHoveredObject.isFootprint()) {
|
||||
// view.requestRedraw();
|
||||
//}
|
||||
|
||||
if (typeof objHoveredStopFunction === 'function') {
|
||||
// call callback function to notify we left the hovered object
|
||||
var ret = objHoveredStopFunction(lastHoveredObject, xymouse);
|
||||
}
|
||||
}
|
||||
|
||||
lastHoveredObject.unhover();
|
||||
}
|
||||
|
||||
lastHoveredObject = null;
|
||||
}
|
||||
|
||||
@@ -1053,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,
|
||||
@@ -1074,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();
|
||||
}
|
||||
|
||||
@@ -1100,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;
|
||||
});
|
||||
@@ -1199,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();
|
||||
@@ -1222,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;
|
||||
@@ -1238,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
|
||||
@@ -1248,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1293,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')
|
||||
}
|
||||
};
|
||||
@@ -1312,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)));
|
||||
@@ -1407,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);
|
||||
@@ -1425,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;
|
||||
}
|
||||
|
||||
@@ -1495,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();
|
||||
@@ -1553,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 });
|
||||
}
|
||||
|
||||
@@ -1653,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 });
|
||||
}
|
||||
@@ -1709,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 });
|
||||
@@ -1774,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)
|
||||
@@ -1832,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
|
||||
@@ -1880,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();
|
||||
};
|
||||
|
||||
@@ -1904,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();
|
||||
@@ -1975,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;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2017,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");
|
||||
|
||||
@@ -43,6 +43,7 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
|
||||
.then((catalogs) => {
|
||||
catalogs.forEach((cat) => {
|
||||
CatalogQueryBox.catalogs[cat.obs_title] = cat;
|
||||
CatalogQueryBox.catalogs[cat.ID] = cat;
|
||||
});
|
||||
|
||||
inputText.update({autocomplete: {options: Object.keys(CatalogQueryBox.catalogs)}})
|
||||
@@ -130,6 +131,7 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
|
||||
}, aladin)
|
||||
|
||||
super({
|
||||
close: false,
|
||||
content: Layout.horizontal({
|
||||
layout: [inputText, loadBtn]
|
||||
}),
|
||||
@@ -237,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) {
|
||||
@@ -262,6 +264,8 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
|
||||
})
|
||||
|
||||
self._hide();
|
||||
|
||||
self.callback && self.callback();
|
||||
},
|
||||
position: {
|
||||
anchor: 'center center',
|
||||
@@ -269,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) {
|
||||
@@ -285,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())
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -85,10 +85,6 @@ export class ServiceQueryBox extends Box {
|
||||
.catch((e) => {
|
||||
window.alert(e)
|
||||
})
|
||||
.finally(() => {
|
||||
// set cursor back to the normal mode
|
||||
//loadingBtn.remove();
|
||||
})
|
||||
},
|
||||
subInputs: []
|
||||
});
|
||||
|
||||
@@ -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