import { SPEED_OF_LIGHT, VELOCITY_SHIFT_TYPE, REDSHIFT_SHIFT_TYPE } from "./constants.js";
/*
** A set of classes and function definitions utilized by the
** differents flavours of OLQV viewers.
**
** Author : M. Caillat
** Date : 06th December 2018
** 07th April 2021
*/
/*
** A class to convert a right ascension expressed in decimal degree into an integer value expressing a pixel index.
*/
class RADDtoPixelConverter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
constructor(radd0, radd1, rapix0, rapix1) {
RADDtoPixelConverter.enter(this.constructor.name);
this.radd0 = radd0;
this.rapix0 = rapix0;
this.slope = (rapix1 - rapix0) / (radd1 - radd0);
RADDtoPixelConverter.exit();
}
convert(radd) {
RADDtoPixelConverter.enter(this.convert.name);
var result = this.rapix0 + (radd - this.radd0) * this.slope;
RADDtoPixelConverter.exit();
return result;
}
}
/*
** A class to convert a declination expressed in decimal degree into an integer value expressing a pixel index.
*/
class DECDDtoPixelConverter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
constructor(decdd0, decdd1, decpix0, decpix1) {
DECDDtoPixelConverter.enter(this.constructor.name);
this.decdd0 = decdd0;
this.decpix0 = decpix0;
this.slope = (decpix1 - decpix0) / (decdd1 - decdd0);
DECDDtoPixelConverter.exit();
}
convert(decdd) {
DECDDtoPixelConverter.enter(this.convert.name);
var result = this.decpix0 + (decdd - this.decdd0) * this.slope;
DECDDtoPixelConverter.exit();
return result;
}
};
class PixelToAbsConverter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
/**
*
* @param {integer} crpix1 the reference pixel along the first axis ( supposedly RA )
* @param {float} cdeltInDeg1 the coordinate increment in degree at the reference point along the first axis ( supposedly RA )
* @param {integer} crpix2 the reference pixel along the second axis ( supposedly DEC )
* @param {float} cdeltInDeg2 the coordinate increment in degree at the reference point along the second axis ( supposedly DEC )
* @param {Projection} projection the instance of Projection deduced from the FITS header.
*/
constructor(crpix1, cdeltInDeg1, crpix2, cdeltInDeg2, projection) {
this.crpix1 = crpix1;
this.cdeltInRad1 = cdeltInDeg1 * Math.PI / 180.;
this.crpix2 = crpix2;
this.cdeltInRad2 = cdeltInDeg2 * Math.PI / 180.;
this.projection = projection;
}
/**
*
* @param {integer} i an integer expressing the first pixel coordinate on the pixels grid ( supposedly RA )
* @param {integer} j an integer expressing the second pixel coordinate on the pixels grid ( supposedly DEC )
* @returns {float[2]} [<right-ascension-in-degree>, <declination-in-degree>]
*/
convert(i, j) {
let aux = this.projection.relToAbs([(i - this.crpix1) * this.cdeltInRad1], [(j - this.crpix2) * this.cdeltInRad2]);
return ([aux['ra'][0] * 180 / Math.PI, aux['dec'][0] * 180 / Math.PI]);
}
/**
* @param {integer} i an integer expressing the first pixel coordinate on the pixels grid ( supposedly RA )
* @param {integer} j an integer expressing the second pixel coordinate on the pixels grid ( supposedly DEC )
* @returns {String[2]} [<string-HMS>, <string-DMS>]
*/
label(i, j) {
let [raInDeg, decInDeg] = this.convert(i, j);
return [DecDeg2HMS(raInDeg), DecDeg2DMS(decInDeg)];
}
}
class AbsToPixelConverter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
/**
*
* @param {integer} crpix1 the reference pixel along the first axis ( supposedly RA )
* @param {float} cdelt1 the coordinate increment in radians at the reference point along the first axis ( supposedly RA )
* @param {integer} crpix2 the reference pixel along the second axis ( supposedly DEC )
* @param {float} cdelt2 the coordinate increment in radians at the reference point along the second axis ( supposedly DEC )
* @param {Projection} projection the instance of Projection deduced from the FITS header.
*/
constructor(crpix1, cdelt1, crpix2, cdelt2, projection) {
this.crpix1 = crpix1;
//degrees to radians
this.cdelt1 = degToRad(cdelt1);
this.crpix2 = crpix2;
// degrees to radians
this.cdelt2 = degToRad(cdelt2);
this.projection = projection;
}
/**
*
* @param {float} x right ascension in radian
* @param {float} y right ascention in radian
* @returns {integer[2]} [<pixel-first-coordinate>, <pixel-second-coordinate>]
*/
convert(x, y) {
let aux = this.projection.absToRel([x], [y]);
let i = this.crpix1 + ((aux["x"][0] / this.cdelt1) - 1);
let j = this.crpix2 + ((aux["y"][0] / this.cdelt2) - 1);
return ([Math.round(i), Math.round(j)]);
}
}
class PixelToDECConverter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
/**
*
* @param {*} refpos CRVAL3
* @param {*} refchannel CRPIX3
* @param {*} angularoffset CDELT3
*/
constructor(refpos, refchannel, angularoffset) {
this.crval3 = refpos;
this.crpix3 = refchannel;
this.cdelt3 = angularoffset;
}
/**
*
* @param {*} pix
*/
convert(pix) {
var result = this.crval3 + (pix - this.crpix3) * this.cdelt3;
return result;
}
};
class PixelToRAConverter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
/**
*
* @param {*} refpos CRVAL1
* @param {*} refchannel CRPIX1
* @param {*} angularoffset CDELT1
* @param {*} dec DEC in degree
*/
constructor(refpos, refchannel, angularoffset, dec) {
this.crval1 = refpos;
this.crpix1 = refchannel;
this.cdelt1 = angularoffset;
}
/**
* static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
* @param {*} pix
*/
convert(pix, dec) {
let dec_inrad = (dec * Math.PI) / 180;
var result = this.crval1 + (pix - this.crpix1) * this.cdelt1 / Math.cos(dec_inrad);
return result;
}
};
/*
** Converts a decimal number expected to represent an angle in degree
** into a string expressing a right ascension ( H:M:S)
*/
var DecDeg2HMS = function(deg, sep = ':') {
//if(any(deg< 0 | deg>360)){stop('All deg values should be 0<=d<=360')}
//if (deg < 0)
//deg[deg < 0] = deg[deg < 0] + 360
const HRS = Math.floor(deg / 15);
const MIN = Math.floor((deg / 15 - HRS) * 60);
let SEC = (deg / 15 - HRS - MIN / 60) * 3600;
SEC = Math.floor(SEC * 1000) / 1000.;
return HRS + sep + MIN + sep + SEC.toFixed(3);
};
/**
* Converts a H:M:S value into decimal degrees
* @param {*} value
* @returns
*/
var HMS2DecDeg = function(value){
const parts = value.split(":");
if(parts.length !== 3){
throw("Invalid format for H:M:S value");
}else{
const a = parseInt(parts[0]);
const b = parseFloat(parts[1])*(1/60);
const c = parseFloat(parts[2]) * (1/3600);
return (a + b + c)*15;
}
};
/*
** Converts a decimal number expected to represent an angle in degree
** into a string expressing a declination ( D:M:S)
*/
var DecDeg2DMS = function(deg, sep = ':') {
var sign = deg < 0 ? '-' : '+';
deg = Math.abs(deg);
var DEG = Math.floor(deg);
var MIN = Math.floor((deg - DEG) * 60);
var SEC = (deg - DEG - MIN / 60) * 3600;
SEC = Math.floor(SEC * 1000.) / 1000.;
if (SEC < 0.) SEC = 0.;
if (SEC > 60) SEC = 60.;
return (sign + DEG + sep + MIN + sep + SEC.toFixed(3));
};
/**
* Converts a declination D:M:S into decimal degrees
* @param {*} value
* @returns result in degrees
*/
var DMS2DecDeg = function(value, sep=":"){
let sign = 1;
if(value[0] == "-"){
sign = -1;
}
const parts = value.split(sep);
if(parts.length !== 3){
throw("Invalid format for D:M:S value");
}else{
const a = parseFloat(parts[0]);
const b = parseFloat(parts[1]) * (1/60);
const c = parseFloat(parts[2]) * (1/3600);
return (a + b + c)*sign;
}
};
/*
** A class to convert pixels into a string expressing a right ascension.
**
** The constructor establishes the transformation pixels -> HMS
** with the given parameter ra0pix, ra1pix ( interval in pixels )
** and ra0, ra1 ( the same interval in decimal degrees)
*/
class RaLabelFormatter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
// calculer les vraies coordonnées dans le bon système
// (sans interpolation linéaire)
constructor(ra0pix, ra1pix, ra0, ra1) {
RaLabelFormatter.enter(this.constructor.name);
this.ra0pix = ra0pix;
this.ra1pix = ra1pix;
this.ra0 = ra0;
this.ra1 = ra1;
this.slope = ((this.ra1 - this.ra0) / (this.ra1pix - this.ra0pix));
this.format = this.format.bind(this);
RaLabelFormatter.exit();
}
/*
** Returns the string representation of a RA in HMS given its input value in pixels.
*/
format(rapix) {
//RaLabelFormatter.enter(this.format.name);
var res = this.ra0 + (rapix - this.ra0pix) * this.slope;
//RaLabelFormatter.exit();
return DecDeg2HMS(res);
}
};
/*
** A class to convert pixels into a string expressing a declination.
**
** The constructor establishes the transformation pixels -> DMS
** with the given parameter dec0pix, dec1pix ( interval in pixels )
** and dec0, dec1 ( the same interval in decimal degrees)
*/
class DecLabelFormatter {
static enter(what) {
console.group(this.name + "." + what);
}
static exit() {
console.groupEnd();
}
// calculer les vraies coordonnées dans le bon système
// (sans interpolation linéaire)
constructor(dec0pix, dec1pix, dec0, dec1) {
DecLabelFormatter.enter(this.constructor.name);
this.dec0pix = dec0pix;
this.dec1pix = dec1pix;
this.dec0 = dec0;
this.dec1 = dec1;
this.slope = ((this.dec1 - this.dec0) / (this.dec1pix - this.dec0pix));
this.format = this.format.bind(this);
DecLabelFormatter.exit();
}
format(decpix) {
//DecLabelFormatter.enter(this.format.name);
var res = this.dec0 + (decpix - this.dec0pix) * this.slope;
//DecLabelFormatter.exit();
return DecDeg2DMS(res);
}
};
/*
** A function which returns the units of a spectrum resulting
** from the summation of the pixels values on each plane of a range of RA-DEC planes in a FITS cube.
*/
/*
function summedPixelsSpectrumUnit(unit) {
switch (unit) {
case "Jy/beam":
return "Jy";
break;
case "erg/s/cm^2/A/arcsec^2":
return "erg/s/cm^2/A";
break;
default:
return "";
}
}
*/
/*
** A function which returns the units of a 2D array resulting from
** the summation of a range of RA-DEC planes in a FITS cube.
*/
function summedPixelsUnit(unit) {
switch (unit) {
case "Jy/beam":
return "Jy/beam * km/s";
case "erg/s/cm^2/A/arcsec^2":
return "erg/s/cm^2/arcsec^2";
case "K (Ta*)":
return "K.km/s";
default:
return "";
}
}
/*
** A function which returns a factor
** for the display of physical quantity
** The returned values are based on experience rather
** than on a purely rational approach
*/
function unitRescale(unit) {
switch (unit) {
case "K (Ta*)":
return 1.0;
case "Jy/beam":
return 1.0;
case "erg/s/cm^2/A/arcsec^2":
// return 1e18;
return 1.;
case "erg/s/cm^2/A":
// return 1e12;
return 1.;
default:
return 1.0;
}
}
/*
** A function which sums the values of a array
** between two indices i0 (included ) and i1 ( excluded )
** and returns the sum multiplied by a coefficient coeff.
*/
function sumArr(arr, i0, i1, coeff) {
console.trace();
console.log(arr);
i0 = Math.max(0, i0);
i1 = Math.min(arr.length - 1, i1);
console.log(i0 + " " + i1);
console.log(arr.slice(i0, i1 + 1))
if (i0 > i1)[i0, i1] = [i1, i0];
try{
return coeff * arr.slice(i0, i1 + 1).reduceRight(function(a, b) { return a + b; });
}catch(exception){
if(arr.slice(i0, i1 + 1).length === 0)
alert(`Selected interval [${i0}, ${i1+1}] is out of bound`);
else
alert(exception);
}
}
/**
* Utility.
* A function which parses a string into an array of floats. The string is expected
* to be a comma separated list of textual representation of decimal numbers.
* A range [min, max[ can be provided, then the values are considered valif if and only if they lie in that range.
*
* @param {string} s a comma separated list of textual numbers representations
* @param {number[2]} range if provided values are checked to lie in the range
*
* @returns {number[]|undefined} an array of float numbers or undefined.
*/
function str2FloatArray(s, range = false) {
let x = s.split(",");
let w = undefined;
let result = undefined;
let y = x.map(function(z) { return parseFloat(z) });
if (range) {
w = y.map(function(t) { return (!isNaN(t) && (range[0] <= t) && (t < range[1])) });
} else {
w = y.map(function(t) { return true; });
}
if (w.reduce(function(accum, u) { return accum && u }, true)) {
result = y;
}
return result;
}
/*
** A function which creates a document fragment out of an HTML string and appends it to the content of an existing element.
** The HTML string is assumed to describe a single element ( e.g. one signle div, p, etc. ).
** Returns the created element.
*/
function createAndAppendFromHTML(html, element) {
var template = document.createElement('template');
template.innerHTML = html.trim();
$(element).append(template.content.cloneNode(true));
return element.lastChild;
}
function redshift2Velocity(z) {
// SPEED_OF_LIGHT in m/s
return z * (SPEED_OF_LIGHT / 1000);
}
function velocity2Redshift(v) {
// SPEED_OF_LIGHT in m/s
return v / (SPEED_OF_LIGHT / 1000);
}
/*
** Two functions to log when a function is entered and exited
*/
function ENTER() {
var caller = ENTER.caller;
if (caller == null) {
result = "_TOP_";
} else {
result = caller.name + ": entering";
}
console.log(result + ": entering");
}
function EXIT() {
var caller = EXIT.caller;
if (caller == null) {
result = "_TOP_";
} else {
result = caller.name + ": exiting";
}
console.log(result + ": exiting");
}
function inRange(x, xmin, xmax) {
return ((x - xmin) * (x - xmax) <= 0);
}
/*
** Frequency <-> Velocity derivations.
**
** From https://www.iram.fr/IRAMFR/ARN/may95/node4.html
*/
/*
** No verification is done on the values of restfreq and frequency. Both are expected to have realistic values.
** frequency and restfreq are expected to by in the same unit (i.e. both in HZ or both in GHZ)
** The result is in m/s.
var f2v = function (frequency, restfreq, crval3) {
return (SPEED_OF_LIGHT * (restfreq - frequency) / restfreq) + crval3;
}*/
var f2v = function(frequency, restfreq, vcenter) {
return (SPEED_OF_LIGHT * (restfreq - frequency) / restfreq) + vcenter;
}
/*
** No verification is done on the values of restfreq and velocity. Both are expected to have realistic values.
*/
var v2f = function(velocity, restfreq, vcenter) {
return restfreq * (1 - (velocity - vcenter) / SPEED_OF_LIGHT)
}
/*
** Frequency to wavelength
*/
var f2lambda = function(frequency) {
return SPEED_OF_LIGHT / frequency;
}
/*
** Revert 1D array.
*/
var revertArray = function(a) {
var result = [];
for (let i = 0; i < a.length; i++) {
result.push(a[a.length - i - 1]);
}
return result;
}
/*
** Round *original* number to *round* numbers after 0.
*/
var round = function(original, round) {
var i = 0;
var r = 1;
while (i < round) {
++i;
r *= 10;
}
return Math.round(original * r) / r;
};
/*
** Coordinate tabulators
**
** - crval : value at reference point
** - cdelt : increment of abscissa
** - crpix : coordinate of reference point
** - n : tabulate at point of coordinate n
*/
var linearTabulator = function(crval, cdelt, crpix, n) {
return crval + (n - crpix) * cdelt;
}
/**
* Generates a random string
* (from https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript)
* @param {*} s
* @returns
*/
var hashCode = function(s) {
return s.split("").reduce(function(a, b) { a = ((a << 5) - a) + b.charCodeAt(0); return a & a }, 0);
}
var degToArcsec = function(deg) {
return deg * 3600;
}
function degToRad(deg) {
return deg * (Math.PI / 180);
}
/**
* get the rest value for a frequency according to a redshift or a velocity
* returns a deep-copy of the array with shifted line values
* @param {*} frequency
* @param {*} value value used to calculate shift
* @param {*} type redshift or velocity
*/
function unshift(frequency, value, type) {
let result = null;
if (type == REDSHIFT_SHIFT_TYPE) {
result = frequency * (1 + value);
} else if (type == VELOCITY_SHIFT_TYPE) {
result = frequency * (1 + (value / (SPEED_OF_LIGHT / 10 ** 3)));
} else {
result = frequency;
}
return result;
}
/**
* calculate the shifted value of all the lines in the given array
* returns a deep-copy of the array with shifted line values
* @param {*} lines array of lines
* @param {*} value value used to calculate shift
* @param {*} type redshift or velocity
*/
function shift(frequency, value, type) {
let result = null;
if (type == REDSHIFT_SHIFT_TYPE) {
result = frequency / (1 + value);
} else if (type == VELOCITY_SHIFT_TYPE) {
result = frequency / (1 + (value / (SPEED_OF_LIGHT / 10 ** 3)));;
} else {
alert("Unknown data type");
}
return result;
}
/**
* Compute the search radius in degrees from coordinates
* to get matching sources in NED
*
* if radius_value is FOV : Returns the max value between abs(RAmax-RAmin) and abs(Decmax-Decmin)
* else : return radius_value
*
* radius_value : radius in arcmin or "fov"
* fits_header : FITS_HEADER object
*/
function getRadiusInDegrees(radius_value, fits_header) {
if (radius_value === "fov") {
// compute dec min and max in degrees
let pix2dec = new PixelToDECConverter(fits_header.crval2, fits_header.crpix2, fits_header.cdelt2);
let dec_min = pix2dec.convert(0);
let dec_max = pix2dec.convert(fits_header.naxis2);
//compute ra min and max in degrees
let pix2ra = new PixelToRAConverter(fits_header.crval1, fits_header.crpix1, fits_header.cdelt1);
let ra_min = pix2ra.convert(0, dec_min);
let ra_max = pix2ra.convert(fits_header.naxis1, dec_max);
// search radius
let dist_in_ra = Math.abs(ra_min - ra_max);
let dist_in_dec = Math.abs(ra_min - ra_max);
return Math.max([dist_in_ra], dist_in_dec)
} else {
return radius_value;
}
}
/**
* Returns value of L'_Line in K.km/s.pc2, then Mgas is a function of L'_Line
* @param {*} sline surface selected in summed pixel spectrum in Jy.km/s
* @param {*} nu_obs line observed frequency in GHz
* @param {*} dl distance in Mpc (from NED)
* @param {*} z redshift
* @returns
*/
function getLpLine(sline, nu_obs, dl, z){
return 3.25e7 * sline * (nu_obs**-2) * (dl**2) * ((1+z)**-3);
}
/**
*Converts K in cm-1 (for energy values)
* @param {*} energy_in_K
*
*/
var KToCm = function(energy_in_K){
return energy_in_K * 0.695;
}
/**
*Converts cm-1 in K (for energy values)
* @param {*} energy_in_cm
*/
var cmToK = function(energy_in_cm){
return energy_in_cm / 0.695;
}
export {
hashCode,
degToRad,
degToArcsec,
linearTabulator,
round,
revertArray,
/*f2lambda,*/ v2f,
f2v,
inRange,
velocity2Redshift,
redshift2Velocity,
/*ENTER, EXIT, createAndAppendFromHTML, str2FloatArray,*/ sumArr,
createAndAppendFromHTML,
str2FloatArray,
unitRescale,
summedPixelsUnit,
DecLabelFormatter,
RaLabelFormatter,
DecDeg2DMS,
DecDeg2HMS,
HMS2DecDeg,
DMS2DecDeg,
PixelToRAConverter,
PixelToDECConverter,
AbsToPixelConverter,
PixelToAbsConverter,
DECDDtoPixelConverter,
RADDtoPixelConverter,
getRadiusInDegrees,
shift,
unshift,
getLpLine,
KToCm,
cmToK
}