@cquiroz/aladin-lite
Version:
AladinLite module
521 lines (402 loc) • 16.4 kB
JavaScript
/******************************************************************************
* Aladin Lite project
*
* File MOC
*
* This class represents a MOC (Multi Order Coverage map) layer
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import Color from './Color';
import Utils from './Utils';
import Aladin from './Aladin';
import AladinUtils from './AladinUtils';
import CooConversion from './CooConversion';
import CooFrameEnum from './CooFrameEnum';
import HealpixCache from './HealpixCache';
import SpatialVector from './SpatialVector';
import { HealpixIndex } from './HealpixIndex';
import astro from './fits';
var MOC = function () {
var MOC = function MOC(options) {
this.order = undefined;
this.type = 'moc'; // TODO homogenize options parsing for all kind of overlay (footprints, catalog, MOC)
options = options || {};
this.name = options.name || "MOC";
this.color = options.color || Color.getNextColor();
this.opacity = options.opacity || 1;
this.opacity = Math.max(0, Math.min(1, this.opacity)); // 0 <= this.opacity <= 1
this.lineWidth = options["lineWidth"] || 1;
this.adaptativeDisplay = options['adaptativeDisplay'] !== false;
this.proxyCalled = false; // this is a flag to check whether we already tried to load the MOC through the proxy
// index of MOC cells at high and low resolution
this._highResIndexOrder3 = new Array(768);
this._lowResIndexOrder3 = new Array(768);
for (var k = 0; k < 768; k++) {
this._highResIndexOrder3[k] = {};
this._lowResIndexOrder3[k] = {};
}
this.nbCellsDeepestLevel = 0; // needed to compute the sky fraction of the MOC
this.isShowing = true;
this.ready = false;
};
function log2(val) {
return Math.log(val) / Math.LN2;
} // max norder we can currently handle (limitation of healpix.js)
MOC.MAX_NORDER = 13; // NSIDE = 8192
MOC.LOWRES_MAXORDER = 6; // 5 or 6 ??
MOC.HIGHRES_MAXORDER = 11; // ??
// TODO: options to modifiy this ?
MOC.PIVOT_FOV = 30; // when do we switch from low res cells to high res cells (fov in degrees)
// at end of parsing, we need to remove duplicates from the 2 indexes
MOC.prototype._removeDuplicatesFromIndexes = function () {
var a, aDedup;
for (var k = 0; k < 768; k++) {
for (var key in this._highResIndexOrder3[k]) {
a = this._highResIndexOrder3[k][key];
aDedup = uniq(a);
this._highResIndexOrder3[k][key] = aDedup;
}
for (var _key in this._lowResIndexOrder3[k]) {
a = this._lowResIndexOrder3[k][_key];
aDedup = uniq(a);
this._lowResIndexOrder3[k][_key] = aDedup;
}
}
}; // add pixel (order, ipix)
MOC.prototype._addPix = function (order, ipix) {
var ipixOrder3 = Math.floor(ipix * Math.pow(4, 3 - order)); // fill low and high level cells
// 1. if order <= LOWRES_MAXORDER, just store value in low and high res cells
if (order <= MOC.LOWRES_MAXORDER) {
if (!(order in this._lowResIndexOrder3[ipixOrder3])) {
this._lowResIndexOrder3[ipixOrder3][order] = [];
this._highResIndexOrder3[ipixOrder3][order] = [];
}
this._lowResIndexOrder3[ipixOrder3][order].push(ipix);
this._highResIndexOrder3[ipixOrder3][order].push(ipix);
} // 2. if LOWRES_MAXORDER < order <= HIGHRES_MAXORDER , degrade ipix for low res cells
else if (order <= MOC.HIGHRES_MAXORDER) {
if (!(order in this._highResIndexOrder3[ipixOrder3])) {
this._highResIndexOrder3[ipixOrder3][order] = [];
}
this._highResIndexOrder3[ipixOrder3][order].push(ipix);
var degradedOrder = MOC.LOWRES_MAXORDER;
var degradedIpix = Math.floor(ipix / Math.pow(4, order - degradedOrder));
var degradedIpixOrder3 = Math.floor(degradedIpix * Math.pow(4, 3 - degradedOrder));
if (!(degradedOrder in this._lowResIndexOrder3[degradedIpixOrder3])) {
this._lowResIndexOrder3[degradedIpixOrder3][degradedOrder] = [];
}
this._lowResIndexOrder3[degradedIpixOrder3][degradedOrder].push(degradedIpix);
} // 3. if order > HIGHRES_MAXORDER , degrade ipix for low res and high res cells
else {
// low res cells
var _degradedOrder = MOC.LOWRES_MAXORDER;
var _degradedIpix = Math.floor(ipix / Math.pow(4, order - _degradedOrder));
var _degradedIpixOrder = Math.floor(_degradedIpix * Math.pow(4, 3 - _degradedOrder));
if (!(_degradedOrder in this._lowResIndexOrder3[_degradedIpixOrder])) {
this._lowResIndexOrder3[_degradedIpixOrder][_degradedOrder] = [];
}
this._lowResIndexOrder3[_degradedIpixOrder][_degradedOrder].push(_degradedIpix); // high res cells
_degradedOrder = MOC.HIGHRES_MAXORDER;
_degradedIpix = Math.floor(ipix / Math.pow(4, order - _degradedOrder));
_degradedIpixOrder = Math.floor(_degradedIpix * Math.pow(4, 3 - _degradedOrder));
if (!(_degradedOrder in this._highResIndexOrder3[_degradedIpixOrder])) {
this._highResIndexOrder3[_degradedIpixOrder][_degradedOrder] = [];
}
this._highResIndexOrder3[_degradedIpixOrder][_degradedOrder].push(_degradedIpix);
}
this.nbCellsDeepestLevel += Math.pow(4, this.order - order);
};
/**
* Return a value between 0 and 1 denoting the fraction of the sky
* covered by the MOC
*/
MOC.prototype.skyFraction = function () {
return this.nbCellsDeepestLevel / (12 * Math.pow(4, this.order));
};
/**
* set MOC data by parsing a MOC serialized in JSON
* (as defined in IVOA MOC document, section 3.1.1)
*/
MOC.prototype.dataFromJSON = function (jsonMOC) {
var order, ipix;
for (var orderStr in jsonMOC) {
if (Object.prototype.hasOwnProperty.call(jsonMOC, orderStr)) {
order = parseInt(orderStr);
if (this.order === undefined || order > this.order) {
this.order = order;
}
for (var k = 0; k < jsonMOC[orderStr].length; k++) {
ipix = jsonMOC[orderStr][k];
this._addPix(order, ipix);
}
}
}
this.reportChange();
this.ready = true;
};
/**
* set MOC data by parsing a URL pointing to a FITS MOC file
*/
MOC.prototype.dataFromFITSURL = function (mocURL, successCallback) {
var self = this;
var callback = function callback() {
// note: in the callback, 'this' refers to the FITS instance
// first, let's find MOC norder
var hdr0;
try {
// A zero-length hdus array might mean the served URL does not have CORS header
// --> let's try again through the proxy
if (this.hdus.length === 0) {
if (self.proxyCalled !== true) {
self.proxyCalled = true;
var proxiedURL = Aladin.JSONP_PROXY + '?url=' + encodeURIComponent(self.dataURL);
new astro.FITS(proxiedURL, callback);
}
return;
}
hdr0 = this.getHeader(0);
} catch (e) {
console.error('Could not get header of extension #0');
return;
}
var hdr1 = this.getHeader(1);
if (hdr0.contains('HPXMOC')) {
self.order = hdr0.get('HPXMOC');
} else if (hdr0.contains('MOCORDER')) {
self.order = hdr0.get('MOCORDER');
} else if (hdr1.contains('HPXMOC')) {
self.order = hdr1.get('HPXMOC');
} else if (hdr1.contains('MOCORDER')) {
self.order = hdr1.get('MOCORDER');
} else {
console.error('Can not find MOC order in FITS file');
return;
}
var data = this.getDataUnit(1);
var colName = data.columns[0];
data.getRows(0, data.rows, function (rows) {
for (var k = 0; k < rows.length; k++) {
var uniq = rows[k][colName];
var order = Math.floor(Math.floor(log2(Math.floor(uniq / 4))) / 2);
var ipix = uniq - 4 * Math.pow(4, order);
self._addPix(order, ipix);
}
});
data = null; // this helps releasing memory
self._removeDuplicatesFromIndexes();
if (successCallback) {
successCallback();
}
self.reportChange();
self.ready = true;
}; // end of callback function
this.dataURL = mocURL; // instantiate the FITS object which will fetch the URL passed as parameter
new astro.FITS(this.dataURL, callback);
};
MOC.prototype.setView = function (view) {
this.view = view;
this.reportChange();
};
MOC.prototype.draw = function (ctx, projection, viewFrame, width, height, largestDim, zoomFactor, fov) {
if (!this.isShowing || !this.ready) {
return;
}
var mocCells = fov > MOC.PIVOT_FOV && this.adaptativeDisplay ? this._lowResIndexOrder3 : this._highResIndexOrder3;
this._drawCells(ctx, mocCells, fov, projection, viewFrame, CooFrameEnum.J2000, width, height, largestDim, zoomFactor);
};
MOC.prototype._drawCells = function (ctx, mocCellsIdxOrder3, fov, projection, viewFrame, surveyFrame, width, height, largestDim, zoomFactor) {
ctx.lineWidth = this.lineWidth; // if opacity==1, we draw solid lines, else we fill each HEALPix cell
if (this.opacity === 1) {
ctx.strokeStyle = this.color;
} else {
ctx.fillStyle = this.color;
ctx.globalAlpha = this.opacity;
}
ctx.beginPath();
var orderedKeys = [];
for (var _k = 0; _k < 768; _k++) {
var _mocCells = mocCellsIdxOrder3[_k];
for (var key in _mocCells) {
orderedKeys.push(parseInt(key));
}
}
orderedKeys.sort(function (a, b) {
return a - b;
});
var norderMax = orderedKeys[orderedKeys.length - 1];
var nside, xyCorners, ipix;
var potentialVisibleHpxCellsOrder3 = this.view.getVisiblePixList(3, CooFrameEnum.J2000);
var visibleHpxCellsOrder3 = []; // let's test first all potential visible cells and keep only the one with a projection inside the view
for (var _k2 = 0; _k2 < potentialVisibleHpxCellsOrder3.length; _k2++) {
var _ipix = potentialVisibleHpxCellsOrder3[_k2];
xyCorners = getXYCorners(8, _ipix, viewFrame, surveyFrame, width, height, largestDim, zoomFactor, projection);
if (xyCorners) {
visibleHpxCellsOrder3.push(_ipix);
}
}
var mocCells;
for (var norder = 0; norder <= norderMax; norder++) {
nside = 1 << norder;
for (var i = 0; i < visibleHpxCellsOrder3.length; i++) {
var ipixOrder3 = visibleHpxCellsOrder3[i];
mocCells = mocCellsIdxOrder3[ipixOrder3];
if (typeof mocCells[norder] === 'undefined') {
continue;
}
if (norder <= 3) {
for (var j = 0; j < mocCells[norder].length; j++) {
ipix = mocCells[norder][j];
var factor = Math.pow(4, 3 - norder);
var startIpix = ipix * factor;
for (var k = 0; k < factor; k++) {
var norder3Ipix = startIpix + k;
xyCorners = getXYCorners(8, norder3Ipix, viewFrame, surveyFrame, width, height, largestDim, zoomFactor, projection);
if (xyCorners) {
drawCorners(ctx, xyCorners);
}
}
}
} else {
for (var _j = 0; _j < mocCells[norder].length; _j++) {
ipix = mocCells[norder][_j]; // let parentIpixOrder3 = Math.floor(ipix/Math.pow(4, norder-3));
xyCorners = getXYCorners(nside, ipix, viewFrame, surveyFrame, width, height, largestDim, zoomFactor, projection);
if (xyCorners) {
drawCorners(ctx, xyCorners);
}
}
}
}
}
if (this.opacity === 1) {
ctx.stroke();
} else {
ctx.fill();
ctx.globalAlpha = 1.0;
}
};
var drawCorners = function drawCorners(ctx, xyCorners) {
ctx.moveTo(xyCorners[0].vx, xyCorners[0].vy);
ctx.lineTo(xyCorners[1].vx, xyCorners[1].vy);
ctx.lineTo(xyCorners[2].vx, xyCorners[2].vy);
ctx.lineTo(xyCorners[3].vx, xyCorners[3].vy);
ctx.lineTo(xyCorners[0].vx, xyCorners[0].vy);
}; // remove duplicate items from array a
var uniq = function uniq(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for (var i = 0; i < len; i++) {
var item = a[i];
if (seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}; // TODO: merge with what is done in View.getVisibleCells
var _spVec = new SpatialVector();
var getXYCorners = function getXYCorners(nside, ipix, viewFrame, surveyFrame, width, height, largestDim, zoomFactor, projection) {
var cornersXYView = [];
var cornersXY = [];
var spVec = _spVec;
var corners = HealpixCache.corners_nest(ipix, nside);
var lon = 0;
var lat = 0;
for (var k = 0; k < 4; k++) {
spVec.setXYZ(corners[k].x, corners[k].y, corners[k].z); // need for frame transformation ?
if (surveyFrame && surveyFrame.system !== viewFrame.system) {
if (surveyFrame.system === CooFrameEnum.SYSTEMS.J2000) {
var radec = CooConversion.J2000ToGalactic([spVec.ra(), spVec.dec()]);
lon = radec[0];
lat = radec[1];
} else if (surveyFrame.system === CooFrameEnum.SYSTEMS.GAL) {
var _radec = CooConversion.GalacticToJ2000([spVec.ra(), spVec.dec()]);
lon = _radec[0];
lat = _radec[1];
}
} else {
lon = spVec.ra();
lat = spVec.dec();
}
cornersXY[k] = projection.project(lon, lat);
}
if (cornersXY[0] == null || cornersXY[1] == null || cornersXY[2] == null || cornersXY[3] == null) {
return null;
}
for (var _k3 = 0; _k3 < 4; _k3++) {
cornersXYView[_k3] = AladinUtils.xyToView(cornersXY[_k3].X, cornersXY[_k3].Y, width, height, largestDim, zoomFactor);
} // var indulge = 10;
// detect pixels outside view. Could be improved !
// we minimize here the number of cells returned
if (cornersXYView[0].vx < 0 && cornersXYView[1].vx < 0 && cornersXYView[2].vx < 0 && cornersXYView[3].vx < 0) {
return null;
}
if (cornersXYView[0].vy < 0 && cornersXYView[1].vy < 0 && cornersXYView[2].vy < 0 && cornersXYView[3].vy < 0) {
return null;
}
if (cornersXYView[0].vx >= width && cornersXYView[1].vx >= width && cornersXYView[2].vx >= width && cornersXYView[3].vx >= width) {
return null;
}
if (cornersXYView[0].vy >= height && cornersXYView[1].vy >= height && cornersXYView[2].vy >= height && cornersXYView[3].vy >= height) {
return null;
}
cornersXYView = AladinUtils.grow2(cornersXYView, 1);
return cornersXYView;
};
MOC.prototype.reportChange = function () {
this.view && this.view.requestRedraw();
};
MOC.prototype.show = function () {
if (this.isShowing) {
return;
}
this.isShowing = true;
this.reportChange();
};
MOC.prototype.hide = function () {
if (!this.isShowing) {
return;
}
this.isShowing = false;
this.reportChange();
}; // Tests whether a given (ra, dec) point on the sky is within the current MOC object
//
// returns true if point is contained, false otherwise
MOC.prototype.contains = function (ra, dec) {
var hpxIdx = new HealpixIndex(Math.pow(2, this.order));
hpxIdx.init();
var polar = Utils.radecToPolar(ra, dec);
var ipix = hpxIdx.ang2pix_nest(polar.theta, polar.phi);
var ipixMapByOrder = {};
for (var curOrder = 0; curOrder <= this.order; curOrder++) {
ipixMapByOrder[curOrder] = Math.floor(ipix / Math.pow(4, this.order - curOrder));
} // first look for large HEALPix cells (order<3)
for (var _ipixOrder = 0; _ipixOrder < 768; _ipixOrder++) {
var _mocCells2 = this._highResIndexOrder3[_ipixOrder];
for (var order in _mocCells2) {
if (order < 3) {
for (var k = _mocCells2[order].length; k >= 0; k--) {
if (ipixMapByOrder[order] === _mocCells2[order][k]) {
return true;
}
}
}
}
} // look for finer cells
var ipixOrder3 = ipixMapByOrder[3];
var mocCells = this._highResIndexOrder3[ipixOrder3];
for (var _order in mocCells) {
for (var _k4 = mocCells[_order].length; _k4 >= 0; _k4--) {
if (ipixMapByOrder[_order] === mocCells[_order][_k4]) {
return true;
}
}
}
return false;
};
return MOC;
}();
export default MOC;