Files
aladin-lite/src/js/ImageSurvey.js

563 lines
20 KiB
JavaScript

// 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 ImageSurvey
*
* Authors: Thomas Boch & Matthieu Baumann [CDS]
*
*****************************************************************************/
import { Utils } from "./Utils.js";
import { ALEvent } from "./events/ALEvent.js";
import { CooFrameEnum } from "./CooFrameEnum.js"
import { ColorCfg } from "./ColorCfg.js";
import { ImageLayer } from "./ImageLayer.js";
import { HiPSProperties } from "./HiPSProperties.js";
let PropertyParser = {};
// Utilitary functions for parsing the properties and giving default values
/// Mandatory tileSize property
PropertyParser.tileSize = function(options, properties = {}) {
let tileSize = (options && options.tileSize) || (properties.hips_tile_width && (+properties.hips_tile_width)) || 512;
// Check if the tile width size is a power of 2
if (tileSize & (tileSize - 1) !== 0) {
tileSize = 512;
}
return tileSize;
}
/// Mandatory frame property
PropertyParser.frame = function(options, properties = {}) {
let frame = (options && options.cooFrame) || (properties.hips_body && "ICRSd") || properties.hips_frame;
if (frame == "ICRS" || frame == "ICRSd" || frame == "equatorial" || frame == "j2000") {
frame = "ICRS";
} else if (frame == "galactic") {
frame = "GAL";
} else if (frame === undefined) {
frame = "ICRS";
console.warn('No cooframe given. Coordinate systems supported: "ICRS", "ICRSd", "j2000" or "galactic". ICRS is chosen by default');
} else {
frame = "ICRSd";
console.warn('Invalid cooframe given: ' + cooFrame + '. Coordinate systems supported: "ICRS", "ICRSd", "j2000" or "galactic". ICRS is chosen by default');
}
return frame;
}
/// Mandatory maxOrder property
PropertyParser.maxOrder = function(options, properties = {}) {
let maxOrder = (options && options.maxOrder) || (properties.hips_order && (+properties.hips_order));
return maxOrder;
}
/// Mandatory minOrder property
PropertyParser.minOrder = function(options, properties = {}) {
const minOrder = (options && options.minOrder) || (properties.hips_order_min && (+properties.hips_order_min)) || 0;
return minOrder;
}
PropertyParser.formats = function(options, properties = {}) {
let formats = properties.hips_tile_format || "jpeg";
formats = formats.split(' ')
.map((fmt) => fmt.toLowerCase());
return formats;
}
PropertyParser.initialFov = function(options, properties = {}) {
let initialFov = properties.hips_initial_fov && +properties.hips_initial_fov;
if (initialFov && initialFov < 0.00002777777) {
initialFov = 360;
}
return initialFov;
}
PropertyParser.skyFraction = function(options, properties = {}) {
const skyFraction = (properties.moc_sky_fraction && (+properties.moc_sky_fraction)) || 0.0;
return skyFraction;
}
PropertyParser.cutouts = function(options, properties = {}) {
let cuts = properties.hips_pixel_cut && properties.hips_pixel_cut.split(" ");
const minCutout = cuts && parseFloat(cuts[0]);
const maxCutout = cuts && parseFloat(cuts[1]);
return [minCutout, maxCutout]
}
PropertyParser.bitpix = function(options, properties = {}) {
const bitpix = properties.hips_pixel_bitpix && (+properties.hips_pixel_bitpix);
return bitpix;
}
PropertyParser.dataproductSubtype = function(options, properties = {}) {
let dataproductSubtype = properties.dataproduct_subtype || "color";
dataproductSubtype = dataproductSubtype.split(" ")
.map((subtype) => subtype.toLowerCase());
return dataproductSubtype;
}
PropertyParser.isPlanetaryBody = function(options, properties = {}) {
return properties.hips_body !== undefined;
}
export let ImageSurvey = (function () {
/** Constructor
* cooFrame and maxOrder can be set to null
* They will be determined by reading the properties file
*
*/
function ImageSurvey(id, name, url, view, options) {
// A reference to the view
this.view = view;
this.wasm = view.wasm;
this.added = false;
this.id = id;
this.name = name;
this.subtype = "survey";
// initialize the color meta data here
this.colorCfg = new ColorCfg(options);
this.properties = {};
let self = this;
self.query = (async () => {
let maxOrder, frame, tileSize, formats, minCutout, maxCutout, bitpix, skyFraction, minOrder, initialFov, initialRa, initialDec, hipsBody, isPlanetaryBody, dataproductSubtype;
try {
let properties;
try {
properties = await HiPSProperties.fetchFromUrl(url)
.catch(async (e) => {
// url not valid so we try with the id
try {
return await HiPSProperties.fetchFromID(id);
} catch(e) {
throw e;
}
})
} catch(e) {
throw e;
}
// Give a better name if we have the HiPS metadata
self.name = self.name || properties.obs_title;
// Set it to a default value
if (!properties.hips_service_url) {
throw 'no valid service URL for retrieving the tiles'
}
url = Utils.fixURLForHTTPS(properties.hips_service_url);
// Request all the properties to see which mirror is the fastest
HiPSProperties.getFasterMirrorUrl(properties)
.then((url) => {
self.setUrl(url);
});
// Max order
maxOrder = PropertyParser.maxOrder(options, properties);
// Tile size
tileSize = PropertyParser.tileSize(options, properties);
// Tile formats
formats = PropertyParser.formats(options, properties);
// min order
minOrder = PropertyParser.minOrder(options, properties);
// Frame
frame = PropertyParser.frame(options, properties);
// sky fraction
skyFraction = PropertyParser.skyFraction(options, properties);
// Initial fov/ra/dec
initialFov = PropertyParser.initialFov(options, properties);
initialRa = +properties.hips_initial_ra;
initialDec = +properties.hips_initial_dec;
// Cutouts
[minCutout, maxCutout] = PropertyParser.cutouts(options, properties);
// Bitpix
bitpix = PropertyParser.bitpix(options, properties);
// Dataproduct subtype
dataproductSubtype = PropertyParser.dataproductSubtype(options, properties);
// HiPS body
isPlanetaryBody = PropertyParser.isPlanetaryBody(options, properties);
if (properties.hips_body) {
hipsBody = properties.hips_body;
}
// TODO move that code out of here
if (properties.hips_body !== undefined) {
if (self.view.options.showFrame) {
self.view.aladin.setFrame('J2000d');
let frameChoiceElt = document.querySelectorAll('.aladin-location > .aladin-frameChoice')[0];
frameChoiceElt.innerHTML = '<option value="' + CooFrameEnum.J2000d.label + '" selected="selected">J2000d</option>';
}
} else {
if (self.view.options.showFrame) {
const cooFrame = CooFrameEnum.fromString(self.view.options.cooFrame, CooFrameEnum.J2000);
let frameChoiceElt = document.querySelectorAll('.aladin-location > .aladin-frameChoice')[0];
frameChoiceElt.innerHTML = '<option value="' + CooFrameEnum.J2000.label + '" '
+ (cooFrame == CooFrameEnum.J2000 ? 'selected="selected"' : '') + '>J2000</option><option value="' + CooFrameEnum.J2000d.label + '" '
+ (cooFrame == CooFrameEnum.J2000d ? 'selected="selected"' : '') + '>J2000d</option><option value="' + CooFrameEnum.GAL.label + '" '
+ (cooFrame == CooFrameEnum.GAL ? 'selected="selected"' : '') + '>GAL</option>';
}
}
} catch (e) {
//console.error("Could not fetch properties for the survey ", self.id, " with the error:\n", e)
/*if (!options.maxOrder) {
throw "The max order is mandatory for a HiPS."
}
if (!options.tileSize) {
console.warn("The tile size has not been given, 512 is chosen by default");
}
url = Utils.fixURLForHTTPS(url);
// Max order
maxOrder = PropertyParser.maxOrder(options);
// Tile size
tileSize = PropertyParser.tileSize(options);
// Tile formats
formats = PropertyParser.formats(options);
// min order
minOrder = PropertyParser.minOrder(options);
// Frame
frame = PropertyParser.frame(options);*/
throw e;
}
self.properties = {
url: url,
maxOrder: maxOrder,
frame: frame,
tileSize: tileSize,
formats: formats,
minCutout: minCutout,
maxCutout: maxCutout,
bitpix: bitpix,
skyFraction: skyFraction,
minOrder: minOrder,
hipsInitialFov: initialFov,
hipsInitialRa: initialRa,
hipsInitialDec: initialDec,
dataproductSubtype: dataproductSubtype,
isPlanetaryBody: isPlanetaryBody,
hipsBody: hipsBody
};
// Use the property to define and check some user given infos
// Longitude reversed
let longitudeReversed = false;
if (options && options.longitudeReversed === true) {
longitudeReversed = true;
}
if (self.properties.hipsBody) {
longitudeReversed = true;
}
self.longitudeReversed = longitudeReversed;
// Image format
let imgFormat = options && options.imgFormat;
if (imgFormat) {
// transform to lower case
imgFormat = imgFormat.toLowerCase();
// convert JPG -> JPEG
if (imgFormat === "jpg") {
imgFormat = "jpeg";
}
// user wants a fits but the properties tells this format is not available
if (imgFormat === "fits" && formats.indexOf('fits') < 0) {
throw self.name + " does not provide fits tiles";
}
if (imgFormat === "webp" && formats.indexOf('webp') < 0) {
throw self.name + " does not provide webp tiles";
}
if (imgFormat === "png" && formats.indexOf('png') < 0) {
throw self.name + " does not provide png tiles";
}
if (imgFormat === "jpeg" && formats.indexOf('jpeg') < 0) {
throw self.name + " does not provide jpeg tiles";
}
} else {
// user wants nothing then we choose one from the properties
if (formats.indexOf('png') >= 0) {
imgFormat = "png";
} else if (formats.indexOf('webp') >= 0) {
imgFormat = "webp";
} else if (formats.indexOf('jpeg') >= 0) {
imgFormat = "jpeg";
} else if (formats.indexOf('fits') >= 0) {
imgFormat = "fits";
} else {
throw "Unsupported format(s) found in the properties: " + formats;
}
}
self.imgFormat = imgFormat;
// Color cuts
//const lowCut = self.colorCfg.minCut || self.properties.minCutout || 0.0;
//const highCut = self.colorCfg.maxCut || self.properties.maxCutout || 1.0;
//self.setCuts(lowCut, highCut);
ImageLayer.update(self);
return self;
})();
};
ImageSurvey.prototype.isReady = function() {
return this.added;
}
ImageSurvey.prototype.setUrl = function (url) {
if (this.properties.url !== url) {
console.info("Change url of ", this.id, " from ", this.properties.url, " to ", url)
// If added to the backend, then we need to tell it the url has changed
if (this.added) {
this.wasm.setHiPSUrl(this.properties.url, url);
}
this.properties.url = url;
}
}
ImageSurvey.prototype.isPlanetaryBody = function() {
return this.properties.isPlanetaryBody;
}
// @api
// TODO: include imgFormat inside the ImageSurvey's meta attribute
ImageSurvey.prototype.setImageFormat = function (format) {
let self = this;
self.query
.then(() => {
updateMetadata(self, () => {
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.properties.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";
}
// Check if it is a fits
self.imgFormat = imgFormat;
});
})
};
// @api
ImageSurvey.prototype.setOpacity = function (opacity) {
let self = this;
updateMetadata(self, () => {
self.colorCfg.setOpacity(opacity);
});
};
// @api
ImageSurvey.prototype.setBlendingConfig = function (additive = false) {
updateMetadata(this, () => {
this.colorCfg.setBlendingConfig(additive);
});
};
// @api
ImageSurvey.prototype.setColormap = function (colormap, options) {
updateMetadata(this, () => {
this.colorCfg.setColormap(colormap, options);
});
}
// @api
ImageSurvey.prototype.setCuts = function (lowCut, highCut) {
updateMetadata(this, () => {
this.colorCfg.setCuts(lowCut, highCut);
});
};
// @api
ImageSurvey.prototype.setGamma = function (gamma) {
updateMetadata(this, () => {
this.colorCfg.setGamma(gamma);
});
};
// @api
ImageSurvey.prototype.setSaturation = function (saturation) {
updateMetadata(this, () => {
this.colorCfg.setSaturation(saturation);
});
};
ImageSurvey.prototype.setBrightness = function (brightness) {
updateMetadata(this, () => {
this.colorCfg.setBrightness(brightness);
});
};
ImageSurvey.prototype.setContrast = function (contrast) {
updateMetadata(this, () => {
this.colorCfg.setContrast(contrast);
});
};
ImageSurvey.prototype.metadata = function () {
return {
...this.colorCfg.get(),
longitudeReversed: this.longitudeReversed,
imgFormat: this.imgFormat
};
}
// Private method for updating the backend with the new meta
var updateMetadata = function (self, callback = undefined) {
if (callback) {
callback();
}
// Tell the view its meta have changed
try {
if (self.added) {
const metadata = self.metadata();
self.wasm.setImageMetadata(self.layer, metadata);
// once the meta have been well parsed, we can set the meta
ALEvent.HIPS_LAYER_CHANGED.dispatchedTo(self.view.aladinDiv, { layer: self });
}
} catch (e) {
// Display the error message
console.error(e);
}
}
ImageSurvey.prototype.add = function (layer) {
this.layer = layer;
this.wasm.addImageSurvey({
layer: this.layer,
properties: this.properties,
meta: this.metadata(),
});
this.added = true;
return Promise.resolve(this);
}
// @api
ImageSurvey.prototype.toggle = function () {
if (this.colorCfg.getOpacity() != 0.0) {
this.colorCfg.setOpacity(0.0);
} else {
this.colorCfg.setOpacity(this.prevOpacity);
}
};
// @oldapi
ImageSurvey.prototype.setAlpha = ImageSurvey.prototype.setOpacity;
ImageSurvey.prototype.setColorCfg = function (colorCfg) {
updateMetadata(this, () => {
this.colorCfg = colorCfg;
});
};
// @api
ImageSurvey.prototype.getColorCfg = function () {
return this.colorCfg;
};
// @api
ImageSurvey.prototype.getOpacity = function () {
return this.colorCfg.getOpacity();
};
ImageSurvey.prototype.getAlpha = ImageSurvey.prototype.getOpacity;
// @api
ImageSurvey.prototype.readPixel = function (x, y) {
return this.wasm.readPixel(x, y, this.layer);
};
ImageSurvey.DEFAULT_SURVEY_ID = "P/DSS2/color";
return ImageSurvey;
})();