Reverse the longitude axis globally

This method does add:
* reverseLongitude method on aladin object
* add a longitudeReversed flag in AladinOptions, when creating the
aladin view to reverse the longitude axis globally at start.
* BREAKS the HiPS options API by removing the longitudeReversed option
at the HiPS level, replacing it by a global flag on the aladin lite view
* fix: when adding two times the same survey, we could not change its
hips options

This commit also impl #191
This commit is contained in:
bmatthieu3
2025-03-04 16:32:23 +01:00
committed by Matthieu Baumann
parent 645bab7cd9
commit 945672a846
22 changed files with 377 additions and 421 deletions

View File

@@ -10,6 +10,8 @@ import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {projection: "TAN", survey: "P/HSC/DR2/deep/g", target: '02 21 36.529 -05 31 20.16', fov: 0.1});
aladin.reverseLongitude(true)
let hscGreenSurvey = aladin.getBaseImageLayer();
hscGreenSurvey.setImageFormat("fits");
hscGreenSurvey.setColormap("green", { stretch: "asinh" });

View File

@@ -201,7 +201,7 @@
let stephansNIRCamMIRI
async function s11() {
stephansNIRCamMIRI = aladin.createImageSurvey('CDS/P/JWST/Stephans-Quintet/NIRCam+MIRI', "stephansMIRI", null, null, null)
stephansNIRCamMIRI = aladin.createImageSurvey('CDS/P/JWST/Stephans-Quintet/NIRCam-MIRI', "stephansNircamMIRI", null, null, null)
stephansNIRCamMIRI.setOpacity(0.0)
aladin.setOverlayImageLayer(stephansNIRCamMIRI, 'stephansNIRCamMIRI')

View File

@@ -23,6 +23,7 @@
<script src="https://code.jquery.com/jquery-1.10.1.min.js"></script>
<script type="text/javascript">
let aladin;
let longitudeReversed = false;
</script>
<div id="aladin-lite-div" style="width:100vw;height:100vh;">
<div id="calibCircle" style="display: none;"></div>
@@ -37,8 +38,8 @@
<!-- fin temporaire gestion cercle -->
<b>Orientation</b><br>
<button id="hips-coronelli" class="pure-button" name="ref-hips" onclick="aladin.setImageSurvey('Coronelli')">Normal</button><br>
<button id="hips-illenoroc" class="pure-button" name="ref-hips" onclick="aladin.setImageSurvey('illenoroC')">Inversé</button>
<button id="hips-coronelli" class="pure-button" name="ref-hips" onclick="longitudeReversed = false; aladin.reverseLongitude(longitudeReversed)">Normal</button><br>
<button id="hips-illenoroc" class="pure-button" name="ref-hips" onclick="longitudeReversed = true; aladin.reverseLongitude(longitudeReversed)">Inversé</button>
<br><br>
<b>Constellations</b>
@@ -232,8 +233,7 @@
A.init.then(() => {
var hipsDir="http://alasky.u-strasbg.fr/CDS_P_Coronelli";
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.createImageSurvey('Coronelli', 'Coronelli', hipsDir, 'equatorial', 4, {imgFormat: 'jpg'});
aladin.setImageSurvey('Coronelli');
$('#layersControlLeft').show();

View File

@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {longitudeReversed: true, showCooGrid: true, cooFrame: 'galactic', target: '0 0', fov: 100});
//aladin.reverseLongitude(true)
setTimeout(
() => {
aladin.reverseLongitude(false)
},
5000
)
});
</script>
</body>
</html>

View File

@@ -5,16 +5,16 @@
<body>
<div>
<div id="offset" style="display: inline-block; width: 200px; height: 100px"></div>
<div id="aladin-lite-div" style="display: inline-block; width: 50%"></div>
<div id="aladin-lite-div" style="width: 500px; height: 500px"></div>
</div>
<!--<script type="text/javascript" src="https://aladin.cds.unistra.fr/AladinLite/api/v3/latest/aladin.js" charset="utf-8"></script>-->
<script>let aladin, hips</script>
<script type="module">
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {projection: 'TAN', cooFrame: 'galactic', showSettingsControl: true, showSimbadPointerControl: true, showContextMenu: true, target: 'galactic center', survey: 'P/Finkbeiner'});
aladin = A.aladin('#aladin-lite-div', {projection: 'TAN', cooFrame: 'galactic', showSettingsControl: true, showSimbadPointerControl: true, showContextMenu: true, target: 'galactic center'});
// possible values are 'blues', 'cividis', 'cubehelix', 'eosb', 'grayscale', 'inferno', 'magma', 'native', 'parula', 'plasma', 'rainbow',
// 'rdbu', 'rdylbu', 'redtemperature', 'sinebow', 'spectral', 'summer', 'viridis', 'ylgnbu' and 'ylorbr'

View File

@@ -10,7 +10,7 @@
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: 'http://alasky.cds.unistra.fr/ancillary/GaiaDR2/hips-density-map/', showProjectionControl: true, showContextMenu: true, showStatusBar: true, fullScreen: true, target: 'galactic center'});
aladin = A.aladin('#aladin-lite-div', {survey: 'http://alasky.cds.unistra.fr/ancillary/GaiaDR2/hips-density-map/', showProjectionControl: true, showContextMenu: true, showStatusBar: true, fullScreen: true, target: 'galactic center', expandLayersControl: true});
const fluxMap = aladin.createImageSurvey('gdr3-color-flux-map', 'Gaia DR3 flux map', 'https://alasky.u-strasbg.fr/ancillary/GaiaEDR3/color-Rp-G-Bp-flux-map', 'equatorial', 7);
const densityMap = aladin.createImageSurvey('gdr3-density-map', 'Gaia DR3 density map', 'sdfsg', 'equatorial', 7, {formats: ['fits']});

View File

@@ -8,7 +8,7 @@
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', fullScreen: true, fov: 360, survey: ['P/DM/vizMine', 'P/HST/GOODS/color', 'P/MATLAS/g'], target: '0 0', showProjectionControl: false, showSettingsControl: false, showLayersControl: false, showCooGrid: false, showFrame: false, showCooLocation: false});
aladin = A.aladin('#aladin-lite-div', {projection: 'MOL', fullScreen: true, fov: 360, survey: ['P/DM/vizMine', 'P/HST/GOODS/color', 'P/MATLAS/g'], target: '0 0', showProjectionControl: false, showSettingsControl: false, showLayersControl: true, showCooGrid: false, showFrame: false, showCooLocation: false});
});
</script>

View File

@@ -10,7 +10,6 @@ A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {fov: 70,projection: "AIT"});
aladin.setOverlayImageLayer(A.imageHiPS(
'Fermi',
"https://alasky.cds.unistra.fr/Fermi/Color",
{
name: "Fermi color",

View File

@@ -15,16 +15,17 @@
let survey1 = aladin.getBaseImageLayer();
survey1.setColormap('magma', {stretch: 'linear'});
let survey2 = aladin.newImageSurvey("CSIRO/P/RACS/low/I");
aladin.setImageLayer(survey2)
let survey2 = aladin.newImageSurvey("CSIRO/P/RACS/low/I", {name: 'racs low'});
survey2.setColormap('rdbu', {stretch: 'linear'});
aladin.setImageLayer(survey2)
let survey3 = aladin.newImageSurvey("CSIRO/P/RACS/mid/I");
aladin.setImageLayer(survey3)
survey3.setColormap('cubehelix', {stretch: 'asinh'});
aladin.setImageLayer(survey2);
setTimeout(() => {
aladin.removeHiPSFromFavorites(survey3)
}, 5000);

View File

@@ -5,7 +5,6 @@
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<div id='aladin-statsDiv'></div>
<script>let aladin, hips</script>
<script type="module">
import A from '../src/js/A.js';

View File

@@ -223,16 +223,10 @@ pub struct ImageMetadata {
pub blend_cfg: BlendCfg,
#[serde(default = "default_opacity")]
pub opacity: f32,
#[serde(default = "default_longitude_reversed")]
pub longitude_reversed: bool,
/// the current format chosen
pub img_format: ImageExt,
}
fn default_longitude_reversed() -> bool {
true
}
fn default_opacity() -> f32 {
1.0
}

View File

@@ -1323,6 +1323,10 @@ impl App {
self.camera.get_longitude_reversed()
}
pub(crate) fn set_longitude_reversed(&mut self, longitude_reversed: bool) {
self.camera.set_longitude_reversed(longitude_reversed, &self.projection);
}
pub(crate) fn add_catalog(&mut self, _name: String, table: JsValue, _colormap: String) {
//let mut exec_ref = self.exec.borrow_mut();
let _table = table;

View File

@@ -566,6 +566,12 @@ impl WebClient {
self.app.get_longitude_reversed()
}
/// Set the longitude axis reversed globally
#[wasm_bindgen(js_name = setLongitudeReversed)]
pub fn set_longitude_reversed(&mut self, longitude_reversed: bool) {
self.app.set_longitude_reversed(longitude_reversed);
}
/// Get the field of view angle value when the view is zoomed out to its maximum
///
/// This method is dependent of the projection currently set.

View File

@@ -308,12 +308,6 @@ impl Layers {
.ok_or(err_layer_not_found)?;
self.layers.remove(id_layer);
// Loop over all the meta for its longitude reversed property
// and set the camera to it if there is at least one
let longitude_reversed = self.meta.values().any(|meta| meta.longitude_reversed);
camera.set_longitude_reversed(longitude_reversed, proj);
// Check if the url is still used
let id_still_used = self.ids.values().any(|rem_id| rem_id == &id);
if id_still_used {
@@ -421,11 +415,6 @@ impl Layers {
// 2. Add the meta information of the layer
self.meta.insert(layer.clone(), meta);
// Loop over all the meta for its longitude reversed property
// and set the camera to it if there is at least one
let longitude_reversed = self.meta.values().any(|meta| meta.longitude_reversed);
camera.set_longitude_reversed(longitude_reversed, proj);
// 3. Add the image hips
let creator_did = String::from(properties.get_creator_did());
@@ -500,11 +489,6 @@ impl Layers {
// 2. Add the meta information of the layer
self.meta.insert(layer.clone(), meta);
// Loop over all the meta for its longitude reversed property
// and set the camera to it if there is at least one
let longitude_reversed = self.meta.values().any(|meta| meta.longitude_reversed);
camera.set_longitude_reversed(longitude_reversed, proj);
// 3. Add the fits image
// The layer does not already exist

View File

@@ -143,7 +143,8 @@ import { Polyline } from "./shapes/Polyline";
* @property {number} [gridOptions.labelSize=15] - The font size of the labels.
*
* @property {string} [projection="SIN"] - Projection type. Can be 'SIN' for orthographic, 'MOL' for mollweide, 'AIT' for hammer-aitoff, 'ZEA' for zenital equal-area or 'MER' for mercator
* @property {boolean} [log=true] - Whether to log events.
* @property {boolean} [longitudeReversed=false] - Longitude reverse axis flag. Set to true to reverse the longitude axis. This is especially needed for planetary survey visualization. Default is set to false.
* @property {boolean} [log=true] - Whether to log events.
* @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.
@@ -518,6 +519,10 @@ export let Aladin = (function () {
if (options.northPoleOrientation) {
this.setRotation(options.northPoleOrientation);
}
if (options.longitudeReversed !== undefined && options.longitudeReversed !== null) {
this.reverseLongitude(options.longitudeReversed)
}
};
Aladin.prototype._setupUI = function (options) {
@@ -700,6 +705,8 @@ export let Aladin = (function () {
projection: "SIN",
log: true,
samp: false,
// Longitude reversed flag
longitudeReversed: false,
realFullscreen: false,
pixelateCanvas: true,
manualSelection: false
@@ -898,7 +905,7 @@ export let Aladin = (function () {
* Sets the coordinate frame of the Aladin instance to the specified frame.
*
* @memberof Aladin
* @param {string} frame - The name of the coordinate frame. Possible values: 'j2000d', 'j2000', 'gal', 'icrs'. The given string is case insensitive.
* @param {string} frame - The name of the coordinate frame. Possible values: 'j2000d', 'j2000', 'gal', 'icrs', 'equatorial'. The given string is case insensitive.
*
* @example
* // Set the coordinate frame to 'J2000'
@@ -1785,6 +1792,16 @@ export let Aladin = (function () {
);
};
/**
* Reverse the longitude axis of the view globally
*
* @memberof Aladin
* @param {Boolean} [longitudeReversed] - Reverse the longitude axis
*/
Aladin.prototype.reverseLongitude = function (longitudeReversed) {
this.view.reverseLongitude(longitudeReversed)
};
/**
* Add a new HiPS layer to the view on top of the others
*
@@ -1959,8 +1976,8 @@ export let Aladin = (function () {
if (!cachedLayerOptions) {
hipsCache.append(imageLayer.id, imageLayer.options)
} else {
// Set the options from what is in the cache
imageLayer.setOptions(cachedLayerOptions);
// Set the image layer object with the options from the cache.
imageLayer.setOptions(cachedLayerOptions)
}
}
}
@@ -1968,8 +1985,6 @@ export let Aladin = (function () {
// Add it to the hipsList if it is not there yet
this.addHiPSToFavorites(imageLayer)
imageLayer.layer = layer;
return this.view.setOverlayImageLayer(imageLayer, layer);
};
@@ -2186,7 +2201,7 @@ export let Aladin = (function () {
aladin.on("layerChanged", (layer, layerName, state) => {
console.log("layerChanged", layer, layerName, state)
})
*/
*/
Aladin.prototype.on = function (what, myFunction) {
if (Aladin.AVAILABLE_CALLBACKS.indexOf(what) < 0) {
return;

View File

@@ -41,17 +41,31 @@
this.stretch = (options && options.stretch) || "linear";
this.stretch = this.stretch.toLowerCase();
this.reversed = false;
// Keep the image tile format because we want the cuts
this.imgFormat = options.imgFormat || 'png';
if (options && options.reversed === true) {
this.reversed = true;
}
this.minCut = {
webp: 0.0,
jpeg: 0.0,
png: 0.0,
fits: undefined // wait the default value coming from the properties
};
if (options && Number.isFinite(options.minCut)) {
this.minCut = options.minCut;
this.minCut[this.imgFormat] = options.minCut;
}
this.maxCut = {
webp: 1.0,
jpeg: 1.0,
png: 1.0,
fits: undefined // wait the default value coming from the properties
};
if (options && Number.isFinite(options.maxCut)) {
this.maxCut = options.maxCut;
this.maxCut[this.imgFormat] = options.maxCut;
}
this.additiveBlending = options && options.additive;
@@ -93,8 +107,8 @@
kContrast: this.kContrast,
stretch: this.stretch,
minCut: this.minCut,
maxCut: this.maxCut,
minCut: this.minCut[this.imgFormat],
maxCut: this.maxCut[this.imgFormat],
reversed: this.reversed,
cmapName: this.colormap,
}
@@ -102,6 +116,9 @@
}
ColorCfg.prototype.setOptions = function(options) {
// Update the imgFormat
this.imgFormat = options.imgFormat || this.imgFormat;
this.setColormap(options.colormap, options)
this.setCuts(options.minCut, options.maxCut)
@@ -231,18 +248,28 @@
return this.reversed;
};
// @api
// Sets the cuts for the current image format
ColorCfg.prototype.setCuts = function(minCut, maxCut) {
if (minCut === null || minCut === undefined || maxCut === null || maxCut === undefined) {
return;
if (minCut instanceof Object) {
// Mincut is given in the form of an javascript object with all the formats
this.minCut = minCut
} else if (minCut !== null && minCut !== undefined) {
this.minCut[this.imgFormat] = minCut;
}
this.minCut = minCut;
this.maxCut = maxCut;
if (maxCut instanceof Object) {
this.maxCut = maxCut;
} else if (maxCut !== null && maxCut !== undefined) {
this.maxCut[this.imgFormat] = maxCut;
}
};
// Returns the cuts for the current image format
ColorCfg.prototype.getCuts = function() {
return [this.minCut, this.maxCut];
return [
this.minCut[this.imgFormat],
this.maxCut[this.imgFormat]
];
};
return ColorCfg;

View File

@@ -49,7 +49,7 @@ export let CooFrameEnum = (function() {
if (str.indexOf('j2000d')==0 || str.indexOf('icrsd')==0) {
return CooFrameEnum.ICRSd;
}
else if (str.indexOf('j2000')==0 || str.indexOf('icrs')==0) {
else if (str.indexOf('j2000')==0 || str.indexOf('icrs')==0 || str.indexOf('equatorial')==0) {
return CooFrameEnum.ICRS;
}
else if (str.indexOf('gal')==0) {

View File

@@ -76,12 +76,12 @@ PropertyParser.minOrder = function (properties) {
return minOrder;
};
PropertyParser.formats = function (properties) {
let formats = (properties && properties.hips_tile_format) || "jpeg";
PropertyParser.acceptedFormats = function (properties) {
let acceptedFormats = (properties && properties.hips_tile_format) || "jpeg";
formats = formats.split(" ").map((fmt) => fmt.toLowerCase());
acceptedFormats = acceptedFormats.split(" ").map((fmt) => fmt.toLowerCase());
return formats;
return acceptedFormats;
};
PropertyParser.initialFov = function (properties) {
@@ -143,20 +143,18 @@ PropertyParser.isPlanetaryBody = function (properties) {
* <li>The coordinate frame of the HiPS</li>
* </ul>
*
* @deprecated The longitudeReversed property is now deprecated from version 3.6.1. This property will be removed from version 3.7.0 and replaced with a method flipping the longitude axis directly on the {@link Aladin} view object and not at the HiPS level.
*
* @typedef {Object} HiPSOptions
* @property {string} [name] - The name of the survey to be displayed in the UI
* @property {Function} [successCallback] - A callback executed when the HiPS has been loaded
* @property {Function} [errorCallback] - A callback executed when the HiPS could not be loaded
* @property {string} [imgFormat] - Formats accepted 'webp', 'png', 'jpeg' or 'fits'. Will raise an error if the HiPS does not contain tiles in this format
* @property {CooFrame} [cooFrame="ICRS"] - Coordinate frame of the survey tiles
* @property {CooFrame} [cooFrame] - Coordinate frame of the survey tiles. If not given, the one from the parsed properties file will be retrieved.
* @property {number} [maxOrder] - The maximum HEALPix order of the HiPS, i.e the HEALPix order of the most refined tile images of the HiPS.
* @property {number} [numBitsPerPixel] - Useful if you want to display the FITS tiles of a HiPS. It specifies the number of bits per pixel. Possible values are:
* -64: double, -32: float, 8: unsigned byte, 16: short, 32: integer 32 bits, 64: integer 64 bits
* @property {number} [tileSize] - The width of the HEALPix tile images. Mostly 512 pixels but can be 256, 128, 64, 32
* @property {number} [minOrder] - If not given, retrieved from the properties of the survey.
* @property {boolean} [longitudeReversed=false] - Deprecated since 3.6.1: Set it to True for planetary survey visualization
* @property {boolean} [longitudeReversed] - Deprecated The longitudeReversed property is now deprecated since version 3.6.1. This property has been removed since version 3.7.0 and replaced with {@link Aladin#reverseLongitude} set directly on the {@link Aladin} view object and not at the HiPS level.
* @property {number} [opacity=1.0] - Opacity of the survey or image (value between 0 and 1).
* @property {string} [colormap="native"] - The colormap configuration for the survey or image.
* @property {string} [stretch="linear"] - The stretch configuration for the survey or image.
@@ -257,12 +255,8 @@ export let HiPS = (function () {
this.cooFrame = CooFrameEnum.fromString(options.cooFrame, null);
this.tileSize = options.tileSize;
this.skyFraction = options.skyFraction;
this.longitudeReversed =
options.longitudeReversed === undefined
? false
: options.longitudeReversed;
this.imgFormat = options.imgFormat;
this.formats = options.formats;
this.acceptedFormats = options.formats;
this.defaultFitsMinCut = options.defaultFitsMinCut;
this.defaultFitsMaxCut = options.defaultFitsMaxCut;
this.numBitsPerPixel = options.numBitsPerPixel;
@@ -271,6 +265,127 @@ export let HiPS = (function () {
this.successCallback = options.successCallback;
this.colorCfg = new ColorCfg(options);
let self = this;
if (this.localFiles) {
// Fetch the properties file
this.query = new Promise(async (resolve, reject) => {
// look for the properties file
await HiPSProperties.fetchFromFile(self.localFiles["properties"])
.then((p) => {
self._parseProperties(p);
self.url = "local";
delete self.localFiles["properties"]
})
.catch((_) => reject("HiPS " + self.id + " error: " + self.localFiles["properties"] + " does not point towards a local HiPS."))
resolve(self);
});
} else {
let isIncompleteOptions = true;
let isID = Utils.isUrl(this.url) === undefined;
if (this.imgFormat === "fits") {
// a fits is given
isIncompleteOptions = !(
this.maxOrder &&
(!isID && this.url) &&
this.imgFormat &&
this.tileSize &&
this.cooFrame &&
this.numBitsPerPixel
);
} else {
isIncompleteOptions = !(
this.maxOrder &&
(!isID && this.url) &&
this.imgFormat &&
this.tileSize &&
this.cooFrame
);
}
this.query = new Promise(async (resolve, reject) => {
if (isIncompleteOptions) {
// ID typed url
if (self.startUrl && isID) {
// First download the properties from the start url
await HiPSProperties.fetchFromUrl(self.startUrl)
.then((p) => {
self._parseProperties(p);
})
.catch((_) => reject("HiPS " + self.id + " error: starting url " + self.startUrl + " given does not points to a HiPS location"))
// 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;
self.url = self.startUrl;
setTimeout(
() => {
if (!self.added)
return;
HiPSProperties.fetchFromID(id)
.then((p) => {
self._fetchFasterUrlFromProperties(p);
})
.catch((_) => reject("HiPS " + self.id + " error: CDS ID " + id + " is not found"));
},
1000
);
} else if (!self.startUrl && isID) {
// 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;
await HiPSProperties.fetchFromID(id)
.then((p) => {
self.url = p.hips_service_url;
self._parseProperties(p);
self._fetchFasterUrlFromProperties(p);
})
.catch(() => {
// If no ID has been found then it may actually be a path
// url pointing to a local HiPS
return HiPSProperties.fetchFromUrl(id)
.then((p) => {
self._parseProperties(p);
})
.catch((_) => reject("HiPS " + self.id + " error: " + id + " does not refer to a found CDS ID nor a local path pointing towards a HiPS"))
})
} else {
await HiPSProperties.fetchFromUrl(self.url)
.then((p) => {
self._parseProperties(p);
})
.catch((_) => reject("HiPS " + self.id + " error: HiPS not found at url " + self.url))
}
} else {
self._parseProperties({
hips_order: self.maxOrder,
hips_service_url: self.url,
hips_tile_width: self.tileSize,
hips_frame: self.cooFrame.label
})
}
if (self.updateHiPSCache) {
self._saveInCache();
self.updateHiPSCache = false;
}
resolve(self);
});
}
};
HiPS.prototype._fetchFasterUrlFromProperties = function(properties) {
@@ -287,7 +402,6 @@ export let HiPS = (function () {
);
self.url = url;
// If added to the backend, then we need to tell it the url has changed
if (self.added) {
self.view.wasm.setHiPSUrl(
@@ -322,8 +436,8 @@ export let HiPS = (function () {
PropertyParser.tileSize(properties) || self.tileSize;
// Tile formats
self.formats =
PropertyParser.formats(properties) || self.formats;
self.acceptedFormats =
PropertyParser.acceptedFormats(properties) || self.acceptedFormats;
// Min order
const minOrder = PropertyParser.minOrder(properties)
@@ -354,8 +468,8 @@ export let HiPS = (function () {
// Cutouts
const cutoutFromProperties = PropertyParser.cutouts(properties);
self.defaultFitsMinCut = cutoutFromProperties[0];
self.defaultFitsMaxCut = cutoutFromProperties[1];
self.defaultFitsMinCut = cutoutFromProperties[0] || 0.0;
self.defaultFitsMaxCut = cutoutFromProperties[1] || 1.0;
// Bitpix
self.numBitsPerPixel =
@@ -364,9 +478,8 @@ export let HiPS = (function () {
// HiPS body
if (properties.hips_body) {
self.hipsBody = properties.hips_body;
// Use the property to define and check some user given infos
// Longitude reversed
self.longitudeReversed = true;
// The HiPS is a planetary one, so we reverse the longitude axis globally
self.view.aladin.reverseLongitude(true)
}
// Give a better name if we have the HiPS metadata
@@ -377,81 +490,40 @@ export let HiPS = (function () {
self.creatorDid = self.creatorDid || self.id || self.url;
// Image format
if (self.imgFormat) {
// transform to lower case
self.imgFormat = self.imgFormat.toLowerCase();
// convert JPG -> JPEG
if (self.imgFormat === "jpg") {
self.imgFormat = "jpeg";
}
// user wants a fits but the properties tells this format is not available
if (
self.imgFormat === "fits" &&
self.formats &&
self.formats.indexOf("fits") < 0
) {
throw self.name + " does not provide fits tiles";
}
if (
self.imgFormat === "webp" &&
self.formats &&
self.formats.indexOf("webp") < 0
) {
throw self.name + " does not provide webp tiles";
}
if (
self.imgFormat === "png" &&
self.formats &&
self.formats.indexOf("png") < 0
) {
throw self.name + " does not provide png tiles";
}
if (
self.imgFormat === "jpeg" &&
self.formats &&
self.formats.indexOf("jpeg") < 0
) {
throw self.name + " does not provide jpeg tiles";
}
} else {
// user wants nothing then we choose one from the properties
if (self.formats.indexOf("webp") >= 0) {
self.imgFormat = "webp";
} else if (self.formats.indexOf("png") >= 0) {
self.imgFormat = "png";
} else if (self.formats.indexOf("jpeg") >= 0) {
self.imgFormat = "jpeg";
} else if (self.formats.indexOf("fits") >= 0) {
self.imgFormat = "fits";
// check the imgFormat with respect to the formats accepted image format
const chooseTileFormat = (acceptedFormats) => {
if (acceptedFormats.indexOf("webp") >= 0) {
return "webp";
} else if (acceptedFormats.indexOf("png") >= 0) {
return "png";
} else if (acceptedFormats.indexOf("jpeg") >= 0) {
return "jpeg";
} else if (acceptedFormats.indexOf("fits") >= 0) {
return "fits";
} else {
throw (
"Unsupported format(s) found in the properties: " +
self.formats
acceptedFormats
);
}
};
// Set an image format with respect to the ones available for that HiPS if:
// * the format is unknown
// * the format is known but is not available for that HiPS
if (!self.imgFormat || !self.acceptedFormats.includes(self.imgFormat)) {
// Switch automatically to a available format
let imgFormat = chooseTileFormat(self.acceptedFormats);
self.setImageFormat(imgFormat)
console.info(self.id + " tile format chosen: " + self.imgFormat)
}
// Cutouts
let minCut, maxCut;
if (self.imgFormat === "fits") {
// Take into account the default cuts given by the property file (this is true especially for FITS HiPSes)
minCut = self.colorCfg.minCut || self.defaultFitsMinCut || 0.0;
maxCut = self.colorCfg.maxCut || self.defaultFitsMaxCut || 1.0;
} else {
minCut = self.colorCfg.minCut || 0.0;
maxCut = self.colorCfg.maxCut || 1.0;
// Set a cuts for fits formats if no cuts has been yet given
let [minCut, maxCut] = self.getCuts();
if (self.imgFormat === "fits" && minCut === undefined && maxCut === undefined) {
self.setCuts(self.defaultFitsMinCut, self.defaultFitsMaxCut);
}
self.setOptions({minCut, maxCut});
self.formats = self.formats || [self.imgFormat];
self._saveInCache();
}
/**
@@ -474,99 +546,23 @@ export let HiPS = (function () {
*
* @memberof HiPS
*
* @param {string} format - The desired image format. Should be one of ["fits", "png", "jpg", "webp"].
* @param {string} imgFormat - The desired image format. Should be one of ["fits", "png", "jpg", "webp"].
*
* @throws {string} Throws an error if the provided format is not one of the supported formats or if the format is not available for the specific HiPS.
*/
HiPS.prototype.setImageFormat = function (format) {
let self = this;
self.query.then(() => {
let imgFormat = format.toLowerCase();
if (
imgFormat !== "fits" &&
imgFormat !== "png" &&
imgFormat !== "jpg" &&
imgFormat !== "jpeg" &&
imgFormat !== "webp"
) {
throw 'Formats must lie in ["fits", "png", "jpg", "webp"]';
}
if (imgFormat === "jpg") {
imgFormat = "jpeg";
}
// Passed the check, we erase the image format with the new one
// We do nothing if the imgFormat is the same
if (self.imgFormat === imgFormat) {
return;
}
// Check the properties to see if the given format is available among the list
// If the properties have not been retrieved yet, it will be tested afterwards
const availableFormats = self.formats;
// user wants a fits but the metadata tells this format is not available
if (
imgFormat === "fits" &&
availableFormats.indexOf("fits") < 0
) {
throw self.id + " does not provide fits tiles";
}
if (
imgFormat === "webp" &&
availableFormats.indexOf("webp") < 0
) {
throw self.id + " does not provide webp tiles";
}
if (
imgFormat === "png" &&
availableFormats.indexOf("png") < 0
) {
throw self.id + " does not provide png tiles";
}
if (
imgFormat === "jpeg" &&
availableFormats.indexOf("jpeg") < 0
) {
throw self.id + " does not provide jpeg tiles";
}
// Switch from png/webp/jpeg to fits
if (
(self.imgFormat === "png" ||
self.imgFormat === "webp" ||
self.imgFormat === "jpeg") &&
imgFormat === "fits"
) {
if (Number.isFinite(self.defaultFitsMinCut) && Number.isFinite(self.defaultFitsMaxCut)) {
// reset cuts to those given from the properties
self.setCuts(self.defaultFitsMinCut, self.defaultFitsMaxCut);
}
// Switch from fits to png/webp/jpeg
} else if (self.imgFormat === "fits") {
self.setCuts(0.0, 1.0);
}
// Check if it is a fits
self.imgFormat = imgFormat;
self._updateMetadata();
});
HiPS.prototype.setImageFormat = function (imgFormat) {
this.setOptions({imgFormat});
};
/**
* Sets the opacity factor when rendering the HiPS
* Get the list of accepted tile format for that HiPS
*
* @memberof HiPS
*
* @returns {string[]} Returns the formats accepted for the survey, i.e. the formats of tiles that are availables. Could be PNG, WEBP, JPG and FITS.
*/
HiPS.prototype.getAvailableFormats = function () {
return this.formats;
return this.acceptedFormats;
};
/**
@@ -634,6 +630,8 @@ export let HiPS = (function () {
* @param {boolean} [options.reversed=false] - Reverse the colormap axis.
*/
HiPS.prototype.setColormap = function (colormap, options) {
colormap = colormap || this.options.colormap;
this.setOptions({colormap, ...options})
};
@@ -728,17 +726,16 @@ export let HiPS = (function () {
if (this.added) {
this.view.wasm.setImageMetadata(this.layer, {
...this.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: this.imgFormat,
});
// 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();
}
// Save it in the JS HiPS cache
this._saveInCache();
} catch (e) {
// Display the error message
console.error(e);
@@ -746,11 +743,13 @@ export let HiPS = (function () {
};
/**
* Set color options generic method for changing colormap, opacity, ... of the HiPS
* Set color options generic method for changing colormap, opacity, ... of the HiPS
*
* @memberof HiPS
*
* @param {Object} options
* @param {number} [options.imgFormat] - Image format of the HiPS tiles. Possible values are "jpeg", "png", "webp" or "fits".
* Some formats might not be handled depending on the survey simply because tiles of that format have not been generated.
* @param {number} [options.opacity=1.0] - Opacity of the survey or image (value between 0 and 1).
* @param {string} [options.colormap="native"] - The colormap configuration for the survey or image.
* @param {string} [options.stretch="linear"] - The stretch configuration for the survey or image.
@@ -766,12 +765,44 @@ export let HiPS = (function () {
HiPS.prototype.setOptions = function(options) {
this.colorCfg.setOptions(options);
// FIXME, change api of setColormap to take an option object having a name field
if (options.colormap == null || options.colormap == undefined) {
delete options.colormap;
/// Set image format
if (options.imgFormat) {
let imgFormat = options.imgFormat.toLowerCase();
if (imgFormat === "jpg") {
imgFormat = "jpeg";
}
if (!["fits", "png", "jpeg", "webp"].includes(imgFormat)) {
console.warn('Formats must lie in ["fits", "png", "jpg", "webp"]. imgFormat option property ignored');
} else {
// Passed the check, we erase the image format with the new one
// We do nothing if the imgFormat is the same
// Check the properties to see if the given format is available among the list
// If the properties have not been retrieved yet, it will be tested afterwards
const availableFormats = this.acceptedFormats;
// user wants a fits but the metadata tells this format is not available
if (!availableFormats || (availableFormats && availableFormats.indexOf(imgFormat) >= 0)) {
this.imgFormat = imgFormat;
let [minCut, maxCut] = this.getCuts();
if (minCut === undefined && maxCut === undefined && imgFormat === "fits") {
// sets the default cuts parsed from the properties
this.setCuts(this.defaultFitsMinCut, this.defaultFitsMaxCut)
}
} else {
console.warn(this.id + " does not provide " + imgFormat + " tiles")
}
}
}
this.options = {...this.options, ...options};
this.options = {
...this.options,
...options,
minCut: this.colorCfg.minCut,
maxCut: this.colorCfg.maxCut
};
this._updateMetadata();
};
@@ -831,146 +862,16 @@ export let HiPS = (function () {
};
HiPS.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 hiPS
if (this.view) {
return;
}
this.view = view;
if (this.localFiles) {
// Fetch the properties file
self.query = (async () => {
// look for the properties file
await HiPSProperties.fetchFromFile(self.localFiles["properties"])
.then((p) => {
self._parseProperties(p);
self.url = "local";
delete self.localFiles["properties"]
})
return self;
})();
return;
}
let isIncompleteOptions = true;
let isID = Utils.isUrl(this.url) === undefined;
if (this.imgFormat === "fits") {
// a fits is given
isIncompleteOptions = !(
this.maxOrder &&
(!isID && this.url) &&
this.imgFormat &&
this.tileSize &&
this.cooFrame &&
this.numBitsPerPixel
);
} else {
isIncompleteOptions = !(
this.maxOrder &&
(!isID && this.url) &&
this.imgFormat &&
this.tileSize &&
this.cooFrame
);
}
self.query = (async () => {
if (isIncompleteOptions) {
// ID typed url
if (self.startUrl && isID) {
// First download the properties from the start url
await HiPSProperties.fetchFromUrl(self.startUrl)
.then((p) => {
self._parseProperties(p);
})
try {
// 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;
self.url = self.startUrl;
setTimeout(
() => {
if (!self.added)
return;
HiPSProperties.fetchFromID(id)
.then((p) => {
self._fetchFasterUrlFromProperties(p);
})
.catch(() => {
// If no ID has been found then it may actually be a path
// url pointing to a local HiPS
return HiPSProperties.fetchFromUrl(id)
.then((p) => {
self._parseProperties(p);
})
})
},
1000
);
} catch (e) {
throw e;
}
} else if (!this.startUrl && isID) {
try {
// 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;
await HiPSProperties.fetchFromID(id)
.then((p) => {
self.url = p.hips_service_url;
self._parseProperties(p);
self._fetchFasterUrlFromProperties(p);
})
.catch(() => {
// If no ID has been found then it may actually be a path
// url pointing to a local HiPS
return HiPSProperties.fetchFromUrl(id)
.then((p) => {
self._parseProperties(p);
})
})
} catch (e) {
throw e;
}
} else {
await HiPSProperties.fetchFromUrl(self.url)
.then((p) => {
self._parseProperties(p);
})
}
} else {
self._parseProperties({
hips_order: this.maxOrder,
hips_service_url: this.url,
hips_tile_width: this.tileSize,
hips_frame: this.cooFrame.label
})
}
return self;
})()
};
/* Precondition: view is attached */
HiPS.prototype._saveInCache = function () {
if (!this.view) {
this.updateHiPSCache = true;
return;
}
let self = this;
let hipsCache = this.view.aladin.hipsCache;
@@ -979,7 +880,7 @@ export let HiPS = (function () {
}
};
HiPS.prototype._add = function (layer) {
HiPS.prototype._add2View = function (layer) {
this.layer = layer;
let self = this;
@@ -991,7 +892,7 @@ export let HiPS = (function () {
maxOrder: self.maxOrder,
cooFrame: self.cooFrame.system,
tileSize: self.tileSize,
formats: self.formats,
formats: self.acceptedFormats,
bitpix: self.numBitsPerPixel,
skyFraction: self.skyFraction,
minOrder: self.minOrder,
@@ -1004,7 +905,6 @@ export let HiPS = (function () {
},
meta: {
...this.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: this.imgFormat,
}
};
@@ -1040,16 +940,13 @@ export let HiPS = (function () {
localFiles
);
return Promise.resolve(this)
.then((hips) => {
this.added = true;
this.added = true;
if (hips.successCallback) {
hips.successCallback(hips)
}
if (this.successCallback) {
this.successCallback(this)
}
return hips
});
return this
};
HiPS.DEFAULT_SURVEY_ID = "P/DSS2/color";

View File

@@ -136,13 +136,12 @@ export let Image = (function () {
this.id = url;
this.name = (options && options.name) || this.url;
this.imgFormat = options && options.imgFormat;
//this.formats = [this.imgFormat];
this.acceptedFormats = [this.imgFormat];
// callbacks
this.successCallback = options && options.successCallback;
this.errorCallback = options && options.errorCallback;
this.longitudeReversed = false;
this.colorCfg = new ColorCfg(options);
this.options = options || {};
@@ -329,11 +328,18 @@ export let Image = (function () {
*/
Image.prototype.readPixel = HiPS.prototype.readPixel;
/** PRIVATE METHODS **/
/**
* Get the list of accepted tile format for that HiPS
*
* @memberof Image
*
* @returns {string[]} Returns the formats accepted for the survey, i.e. the formats of tiles that are availables. Could be PNG, WEBP, JPG and FITS.
*/
Image.prototype.getAvailableFormats = HiPS.prototype.getAvailableFormats;
Image.prototype._setView = function (view) {
this.view = view;
this._saveInCache();
};
// FITS images does not mean to be used for storing planetary data
@@ -356,7 +362,7 @@ export let Image = (function () {
// Private method for updating the view with the new meta
Image.prototype._updateMetadata = HiPS.prototype._updateMetadata;
Image.prototype._add = function (layer) {
Image.prototype._add2View = function (layer) {
this.layer = layer;
let self = this;
@@ -388,11 +394,10 @@ export let Image = (function () {
}
promise = promise.then((imageParams) => {
self.formats = [self.imgFormat];
self.acceptedFormats = [self.imgFormat];
// There is at least one entry in imageParams
self.added = true;
self._setView(self.view);
// Set the automatic computed cuts
let [minCut, maxCut] = self.getCuts();
@@ -442,7 +447,6 @@ export let Image = (function () {
stream,
{
...self.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: 'fits',
},
layer
@@ -460,7 +464,6 @@ export let Image = (function () {
stream,
{
...self.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: 'fits',
},
layer
@@ -470,7 +473,8 @@ export let Image = (function () {
}
})
.then((imageParams) => {
self.imgFormat = 'fits';
self.imgFormat = 'fits'
self.colorCfg.setOptions({imgFormat: 'fits'});
return Promise.resolve(imageParams);
})
@@ -562,14 +566,14 @@ export let Image = (function () {
wcs,
{
...self.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: 'jpeg',
},
layer
)
})
.then((imageParams) => {
self.imgFormat = 'jpeg';
self.imgFormat = 'jpeg'
self.colorCfg.setOptions({imgFormat: 'jpeg'});
return Promise.resolve(imageParams);
})
.finally(() => {

View File

@@ -717,9 +717,7 @@ export let View = (function () {
// Take as start cut values what is inside the properties
// If the cuts are not defined in the metadata of the survey
// then we take what has been defined by the user
cutMinInit = imageLayer.getColorCfg().minCut || 0.0;
cutMaxInit = imageLayer.getColorCfg().maxCut || 1.0;
[cutMinInit, cutMaxInit] = imageLayer.getCuts();
}
}
@@ -1188,7 +1186,6 @@ export let View = (function () {
view.throttledTouchPadZoom = () => {
const factor = Utils.detectTrackPad(e) ? 1.04 : 1.2;
const currZoomFactor = view.zoom.isZooming ? view.zoom.finalZoom : view.zoomFactor;
//const currZoomFactor = view.wasm.getZoomFactor();
let newZoomFactor = view.delta > 0 ? currZoomFactor * factor : currZoomFactor / factor;
// inside case
@@ -1394,6 +1391,10 @@ export let View = (function () {
}
};
View.prototype.reverseLongitude = function(longitudeReversed) {
this.wasm.setLongitudeReversed(longitudeReversed);
}
View.prototype.refreshProgressiveCats = function () {
if (!this.catalogs) {
return;
@@ -1607,6 +1608,7 @@ export let View = (function () {
View.prototype.setOverlayImageLayer = function (imageLayer, layer = "overlay") {
// set the view to the image layer object
// do the properties query if needed
imageLayer.layer = layer;
imageLayer._setView(this);
// register its promise
@@ -1617,7 +1619,9 @@ export let View = (function () {
return imageLayer;
};
// Insert a layer object (Image/HiPS) at a specific index in the stack
View.prototype._addLayer = function(imageLayer) {
// Keep the JS frontend in-line with the wasm state
const layerName = imageLayer.layer;
// Check whether this layer already exist
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layerName);
@@ -1628,12 +1632,16 @@ export let View = (function () {
} else {
// it exists
alreadyPresentImageLayer = this.imageLayers.get(layerName);
alreadyPresentImageLayer.added = false;
// Notify that this image layer has been replaced by the wasm part
if (alreadyPresentImageLayer && alreadyPresentImageLayer.added === true) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
}
alreadyPresentImageLayer.added = false;
this.imageLayers.delete(layerName);
}
//imageLayer.added = true;
imageLayer.added = true;
this.imageLayers.set(layerName, imageLayer);
@@ -1641,11 +1649,6 @@ export let View = (function () {
if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
}
// Notify that this image layer has been replaced by the wasm part
if (alreadyPresentImageLayer) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
}
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
@@ -1655,6 +1658,7 @@ export let View = (function () {
// start the query
const imageLayerPromise = imageLayer.query;
let idx = this.promises.length;
this.promises.push(imageLayerPromise);
// All image layer promises must be completed (fullfilled or rejected)
@@ -1662,25 +1666,21 @@ export let View = (function () {
message: 'Load layer: ' + imageLayer.name,
id: Utils.uuidv4(),
}
Promise.allSettled(this.promises)
.then(() => imageLayerPromise)
// The promise is resolved and we now have access
// to the image layer objet (whether it is an HiPS or an Image)
// Ensure all the properties for HiPSes have been seeked
ALEvent.FETCH.dispatchedTo(document, {task});
// All the remaining promises must be terminated and the current one must be resolved
// so that we can add it to the view (call of _add2View)
Promise.all([Promise.allSettled(this.promises), imageLayerPromise])
// Then we add the layer to the view
.then((_) => imageLayer._add2View(layer))
// Then we keep a track of the layer in the JS front
.then((imageLayer) => {
// Add to the backend
imageLayer._setView(this)
const promise = imageLayer._add(layer);
ALEvent.FETCH.dispatchedTo(document, {task});
this._addLayer(imageLayer);
return promise;
})
.then((imageLayer) => {
// If the image layer has successfuly been added
this.empty = false;
this._addLayer(imageLayer);
// change the view frame in case we have a planetary hips loaded
// Change the view frame in case we have a planetary hips loaded
if (imageLayer.hipsBody) {
if (this.options.showFrame) {
this.aladin.setFrame('J2000d');
@@ -1704,7 +1704,6 @@ export let View = (function () {
self.imageLayersBeingQueried.delete(layer);
// Remove the settled promise
let idx = this.promises.findIndex(p => p == imageLayerPromise);
this.promises.splice(idx, 1);
const noMoreLayersToWaitFor = this.promises.length === 0;

View File

@@ -310,7 +310,8 @@ import { Form } from "../Widgets/Form.js";
let fmtInput = this.pixelSettingsContent.getInput('fmt')
fmtInput.innerHTML = '';
for (const option of layer.formats) {
for (const option of layer.getAvailableFormats()) {
fmtInput.innerHTML += "<option>" + option + "</option>";
}
fmtInput.value = layer.imgFormat;

View File

@@ -85,7 +85,7 @@ export class OverlayStackBox extends Box {
hoverColor: 'red',
onClick: "showTable",
shape: (s) => {
let galaxy = ["Seyfert","Seyfert_1", "Seyfert_2","LSB_G","PartofG","RadioG","Gin","GinPair","HII_G","LensedG","BClG","BlueCompG","EmG","GinCl","GinGroup","StarburstG","LINER","AGN","Galaxy"].some((n) => s.data.main_type.indexOf(n) >= 0);
let galaxy = ["Seyfert","Seyfert_1", "Seyfert_2","LSB_G","PartofG","RadioG","Gin","GinPair","HII_G","LensedG","BClG","BlueCompG","EmG","GinCl","GinGroup","StarburstG","LINER","AGN", "Galaxy", "GtowardsGroup", "GtowardsCl", "BrightestCG"].some((n) => s.data.main_type.indexOf(n) >= 0);
if (!galaxy) return;
let a = +s.data.size_maj;
@@ -710,6 +710,7 @@ export class OverlayStackBox extends Box {
// Add a listener for HiPS list changes
ALEvent.FAVORITE_HIPS_LIST_UPDATED.listenedBy(document.body, (event) => {
let favoritesHips = event.detail;
self.cachedHiPS = {};
for (var hips of favoritesHips) {