diff --git a/CHANGELOG.md b/CHANGELOG.md index 09444a59d..08325f216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 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 diff --git a/assets/icons/add.svg b/assets/icons/add.svg new file mode 100644 index 000000000..92c79aa44 --- /dev/null +++ b/assets/icons/add.svg @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/examples/al-catalog-hips-shape.html b/examples/al-catalog-hips-shape.html index 341f8f884..7d19b0250 100644 --- a/examples/al-catalog-hips-shape.html +++ b/examples/al-catalog-hips-shape.html @@ -10,26 +10,8 @@ import A from '../src/js/A.js'; let aladin; A.init.then(() => { - aladin = A.aladin('#aladin-lite-div', {target: '12 25 41.512 +12 48 47.2', fov: 1, 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) { - return; - } - - canvasCtx.globalAlpha = 0.9; - canvasCtx.globalAlpha = 1; - }; var drawFunctionFootprint = function(s) { let a = +s.data.size_maj; @@ -39,8 +21,8 @@ if (!galaxy) return; - let angle = +s.data.size_angle || 0.0; - return A.ellipse(s.ra, s.dec, a / 60, b / 60, angle, {color: 'cyan'}); + 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', color: 'cyan', hoverColor: 'red', shape: drawFunctionFootprint}); diff --git a/examples/al-coronelli.html b/examples/al-coronelli.html index 69593a90a..566d29b69 100644 --- a/examples/al-coronelli.html +++ b/examples/al-coronelli.html @@ -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'); diff --git a/examples/al-footprints.html b/examples/al-footprints.html index 7a88cdc3d..0a3790509 100644 --- a/examples/al-footprints.html +++ b/examples/al-footprints.html @@ -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', hoverColor: 'purple', limit: 1000}); + const cat = A.catalogFromVizieR('B/assocdata/obscore', 'M 1', 10, {onClick: 'showTable', hoverColor: 'purple', limit: 10000}); aladin.addCatalog(cat); }); diff --git a/examples/al-save-colormap.html b/examples/al-save-colormap.html new file mode 100644 index 000000000..6451864fc --- /dev/null +++ b/examples/al-save-colormap.html @@ -0,0 +1,32 @@ + + +
+ + + + + + + + + + diff --git a/src/css/aladin.css b/src/css/aladin.css index f702ee55f..7195f1aec 100644 --- a/src/css/aladin.css +++ b/src/css/aladin.css @@ -429,7 +429,7 @@ canvas { } .aladin-input-text.aladin-dark-theme.search { - width: 14rem; + width: 15rem; text-shadow: 0px 0px 2px #000; } @@ -824,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 { @@ -853,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; @@ -1104,6 +1107,19 @@ canvas { height: 1.7rem; } +.aladin-input-text.aladin-dark-theme.search.aladin-HiPS-search { + width: 100%; + +} + +.aladin-stack-box { + width: 17rem; +} + +.aladin-stack-box .content > * { + margin-bottom: 0.5rem; +} + .aladin-location { position: absolute; top: 0.2rem; @@ -1122,6 +1138,8 @@ canvas { bottom: 0.2rem; left: 0.2rem; + background-color: red; + font-family: monospace; font-size: 1rem; @@ -1129,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; diff --git a/src/js/Aladin.js b/src/js/Aladin.js index 721660e88..453de9f4c 100644 --- a/src/js/Aladin.js +++ b/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"; @@ -77,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. @@ -86,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. @@ -133,7 +140,53 @@ import { CooFrame } from './gui/Input/CooFrame'; * @property {boolean} [samp=false] - Whether to enable SAMP (Simple Application Messaging Protocol). * @property {boolean} [realFullscreen=false] - Whether to use real fullscreen mode. * @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas. - */ + * @example + * let aladin = A.aladin({ + target: 'galactic center', + fov: 10, + hipsList: [ + // url + "https://alaskybis.unistra.fr/DSS/DSSColor", + // ID from HiPS list + "CDS/P/2MASS/color", + // Not full HiPS described + { + name: 'DESI Legacy Surveys color (g, r, i, z)', + id: 'CDS/P/DESI-Legacy-Surveys/DR10/color', + }, + // Fully described HiPS + { + name: "DECaPS DR2 color", + url: "https://alasky.cds.unistra.fr/DECaPS/DR2/CDS_P_DECaPS_DR2_color/", + properties: { + creatorDid: "ivo://CDS/P/DECaPS/DR2/color", + maxOrder: 11, + cooFrame: "equatorial", + tileSize: 512, + imgFormat: 'png', + }, + }, + // HiPS with options + { + name: "SDSS9 band-g", + id: "P/SDSS9/g", + properties: { + creatorDid: "ivo://CDS/P/SDSS9/g", + maxOrder: 10, + tileSize: 512, + numBitsPerPixel: 16, + imgFormat: 'fits', + cooFrame: 'equatorial', + }, + options: { + minCut: 0, + maxCut: 1.8, + stretch: 'linear', + colormap: "redtemperature", + } + } + ] +})*/ /** * @typedef {Object} CircleSelection @@ -335,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 @@ -412,6 +543,7 @@ export let Aladin = (function () { if (!options.showLayersControl) { stack._hide(); } + // Add the simbad pointer control if (!options.showSimbadPointerControl) { simbad._hide(); @@ -443,6 +575,10 @@ export let Aladin = (function () { this.addUI(new FullScreenActionButton(self)) } + if (options.expandLayersControl) { + stack.toggle(); + } + this._applyMediaQueriesUI(); } @@ -494,6 +630,8 @@ 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", @@ -504,6 +642,7 @@ export let Aladin = (function () { showZoomControl: false, // Menu toolbar showLayersControl: true, + expandLayersControl: false, showFullscreenControl: true, showSimbadPointerControl: false, showCooGridControl: false, @@ -539,7 +678,7 @@ export let Aladin = (function () { 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 @@ -851,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()); @@ -914,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) { @@ -936,7 +1076,7 @@ export let Aladin = (function () { radec = [lon, lat]; } - this.view.pointTo(radec[0], radec[1]); + this.gotoRaDec(radec[0], radec[1]); }; var idTimeoutAnim; @@ -1140,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); @@ -1239,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; } diff --git a/src/js/HiPSProperties.js b/src/js/HiPSProperties.js index 78a1cf988..5e9ec1925 100644 --- a/src/js/HiPSProperties.js +++ b/src/js/HiPSProperties.js @@ -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 { diff --git a/src/js/ImageSurvey.js b/src/js/ImageSurvey.js index b927d5f18..cab089a9a 100644 --- a/src/js/ImageSurvey.js +++ b/src/js/ImageSurvey.js @@ -144,8 +144,7 @@ export let ImageSurvey = (function () { * @class * @constructs ImageSurvey * - * @param {string} id - Mandatory unique identifier for the layer. - * Can be an arbitrary name + * @param {string} id - Mandatory unique identifier for the layer. Can be an arbitrary name * @param {string} url - Can be an url to the survey or a "CDS" ID pointing towards a HiPS. One can found the list of IDs {@link https://aladin.cds.unistra.fr/hips/list| here} * @param {ImageSurveyOptions} [options] - The option for the survey * @@ -193,32 +192,29 @@ export let ImageSurvey = (function () { self.query = (async () => { if (isMOCServerToBeQueried) { - let properties; let isCDSId = false; - try { - properties = await HiPSProperties.fetchFromUrl(self.url) - /*.catch((e) => { - // try with the proxy - url = Utils.handleCORSNotSameOrigin(url).href; - - return HiPSProperties.fetchFromUrl(url); - })*/ - .catch(async (e) => { - // url not valid so we try with the id - try { - isCDSId = true; - // the url stores a "CDS ID" we take it prioritaly - // if the url is null, take the id, this is for some tests - // to pass because some users might just give null as url param and a "CDS ID" as id param - let id = self.url || self.id; - return await HiPSProperties.fetchFromID(id); - } catch(e) { - throw e; - } - }) - } catch(e) { - throw e; - } + + let properties = await HiPSProperties.fetchFromUrl(self.url) + /*.catch((e) => { + // try with the proxy + url = Utils.handleCORSNotSameOrigin(url).href; + + return HiPSProperties.fetchFromUrl(url); + })*/ + .catch(async (e) => { + // url not valid so we try with the id + try { + isCDSId = true; + // the url stores a "CDS ID" we take it prioritaly + // if the url is null, take the id, this is for some tests + // to pass because some users might just give null as url param and a "CDS ID" as id param + let id = self.url || self.id; + return await HiPSProperties.fetchFromID(id); + } catch(e) { + throw e; + } + }) + //obsTitle = properties.obs_title; self.creatorDid = properties.creator_did || self.creatorDid; @@ -238,24 +234,25 @@ export let ImageSurvey = (function () { if (self.url !== url) { console.info("Change url of ", self.id, " from ", self.url, " to ", url) + self.url = url; + + // save the new url to the cache + self._saveInCache(); + // If added to the backend, then we need to tell it the url has changed if (self.added) { self.view.wasm.setHiPSUrl(self.creatorDid, url); } - - self.url = url; - - // save the new url to the cache - ImageSurvey.cache[self.id].url = self.url; } }) - /*.catch(e => { + .catch(e => { //alert(e); + console.error(self) console.error(e); // the survey has been added so we remove it from the stack - self.view.removeImageLayer(self.layer) + //self.view.removeImageLayer(self.layer) //throw e; - })*/ + }) } // Max order @@ -402,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. @@ -704,7 +707,7 @@ export let ImageSurvey = (function () { }, }); - this.added = true; + //this.added = true; return Promise.resolve(this); } @@ -748,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", @@ -913,6 +916,7 @@ export let ImageSurvey = (function () { stretch: 'linear', colormap: "redtemperature", } + */ /* { id: "P/Finkbeiner", diff --git a/src/js/MOC.js b/src/js/MOC.js index 8df6f2e38..b56518a02 100644 --- a/src/js/MOC.js +++ b/src/js/MOC.js @@ -110,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) @@ -120,7 +120,7 @@ export let MOC = (function() { } this.successCallback = successCallback; - this.errorCallback = this.errorCallback; + this.errorCallback = errorCallback; }; MOC.prototype.setView = function(view) { @@ -166,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() { diff --git a/src/js/Utils.ts b/src/js/Utils.ts index 239ee29a5..250f177f5 100644 --- a/src/js/Utils.ts +++ b/src/js/Utils.ts @@ -469,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) { diff --git a/src/js/View.js b/src/js/View.js index ae579263e..45289c55b 100644 --- a/src/js/View.js +++ b/src/js/View.js @@ -488,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; }; @@ -1167,7 +1169,7 @@ export let View = (function () { } //requestAnimFrame(moveTo) }*/ - }, 30); + }, 40); } view.throttledTouchPadZoom(); @@ -1578,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 }); } @@ -1678,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 }); } @@ -1734,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 }); @@ -1857,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 @@ -1905,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(); }; @@ -1929,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(); @@ -2000,7 +2013,7 @@ export let View = (function () { let closest = null; footprints.forEach((footprint) => { - if (!footprint.source.tooSmallFootprint) { + if (!footprint.source || !footprint.source.tooSmallFootprint) { // Hidden footprints are not considered let lineWidth = footprint.getLineWidth(); diff --git a/src/js/events/ALEvent.js b/src/js/events/ALEvent.js index d38713eb8..bc2471bd4 100644 --- a/src/js/events/ALEvent.js +++ b/src/js/events/ALEvent.js @@ -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"); diff --git a/src/js/gui/Box/CatalogQueryBox.js b/src/js/gui/Box/CatalogQueryBox.js index 7282aa2a0..0f51db302 100644 --- a/src/js/gui/Box/CatalogQueryBox.js +++ b/src/js/gui/Box/CatalogQueryBox.js @@ -131,6 +131,7 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js"; }, aladin) super({ + close: false, content: Layout.horizontal({ layout: [inputText, loadBtn] }), @@ -238,10 +239,10 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js"; this.loadBtn.update({disable: true}, aladin) } else { let self = this; - let layout = []; + let ctxMenu = []; if (item && item.cs_service_url) { - layout.push({ + ctxMenu.push({ label: 'Cone search', disable: !item.cs_service_url, action(o) { @@ -263,6 +264,8 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js"; }) self._hide(); + + self.callback && self.callback(); }, position: { anchor: 'center center', @@ -270,13 +273,12 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js"; }) self.box._show(); self.loadBtn.hideMenu() - } }) } if (item && item.hips_service_url) { - layout.push({ + ctxMenu.push({ label: 'HiPS catalogue', disable: !item.hips_service_url, action(o) { @@ -286,15 +288,22 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js"; }) self._hide(); + + self.callback && self.callback(); } }) } - this.loadBtn.update({ctxMenu: layout, disable: false}, aladin) + this.loadBtn.update({ctxMenu, disable: false}, aladin) } this.loadBtn.hideMenu() } + attach(options) { + this.callback = options.callback; + super.update(options) + } + _hide() { if (this.box) { this.box.remove(); diff --git a/src/js/gui/Box/HiPSSelectorBox.js b/src/js/gui/Box/HiPSSelectorBox.js index 7b8a2eb54..3be958dc9 100644 --- a/src/js/gui/Box/HiPSSelectorBox.js +++ b/src/js/gui/Box/HiPSSelectorBox.js @@ -79,6 +79,7 @@ import { Input } from "../Widgets/Input.js"; super( { + close: false, content: Layout.horizontal({ layout: [ inputText, diff --git a/src/js/gui/Box/SurveyEditBox.js b/src/js/gui/Box/HiPSSettingsBox.js similarity index 91% rename from src/js/gui/Box/SurveyEditBox.js rename to src/js/gui/Box/HiPSSettingsBox.js index 5dafbc13f..7df1b0daa 100644 --- a/src/js/gui/Box/SurveyEditBox.js +++ b/src/js/gui/Box/HiPSSettingsBox.js @@ -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()) } }); } diff --git a/src/js/gui/Box/ShortLivedBox.js b/src/js/gui/Box/ShortLivedBox.js index 27785f032..01aa43c9c 100644 --- a/src/js/gui/Box/ShortLivedBox.js +++ b/src/js/gui/Box/ShortLivedBox.js @@ -35,6 +35,7 @@ export class ShortLivedBox extends Box { constructor(aladin) { super( { + close: false, cssStyle: { color: 'white', backgroundColor: 'black', diff --git a/src/js/gui/Box/StackBox.js b/src/js/gui/Box/StackBox.js new file mode 100644 index 000000000..ef45efce8 --- /dev/null +++ b/src/js/gui/Box/StackBox.js @@ -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