import { HMS2DecDeg,DMS2DecDeg} from "./utils.js";
import { withSAMP, dataPaths, URL_ROOT, URL_3D_TO_2D, yafitsTarget } from './init.js';
import { ServerApi } from './serverApi.js'
import { FITS_HEADER } from './fitsheader.js';
import { MarkerManager, Marker } from "./olqv_markers.js";
import { sAMPPublisher, setOnHubAvailabilityButtons } from "./samp_utils.js";
import { getProjection } from "./olqv_projections.js";
import { DOMAccessor } from "./domelements.js";
import { EventFactory } from './customevents.js';
import { Viewer } from './olqv_viewer.js';
import { AxesFactory } from './olqv_axes.js';
import {highlightedFeature, standardFeature} from "./olqv_shapestyle.js";
import { ResetButton, View2DButton, SaveImageButton, PublishSAMPButton, DeleteButton} from "./olqv_olbuttons.js";
import { CustomControls } from "./olqv_customcontrols.js";
import { KeyCodeProcessor } from "./olqv_keyboardevents.js";
import { Boxes3DFactory } from "./olqv_boxes.js";
import { InfosBlock } from "./olqv_infosblock.js";
/**
* Returns an event signaling that a slice has been modified
* data packed inside the event are used in testing context
* @param {object} slice an instance of Slice classes
* @param {string} type type of the modified slice ( single or summed)
* @param {object} data slice data
* @returns Event
*/
function getSliceUpdateEvent(slice, sliceIndex, type, data){
let event = new Event(type);
event.rmsValue = slice.getRms();
event.rmsUnit = slice.getRmsUnit();
event.channel = sliceIndex;
event.sliceMinValue = data['statistics']['min'];
event.sliceMaxValue = data['statistics']['max'];
let xRef = Math.round(FITS_HEADER.naxis1 / 2);
let yRef = Math.round(FITS_HEADER.naxis2 / 2);
event.sliceRefRaDec = slice._projection.iRaiDecToHMSDMS(xRef, yRef);
event.sliceRefXi = xRef;
event.sliceRefYi = yRef;
event.polygon = slice._polygon;
return event;
}
/**
* Returns the coordinates of the defautl box created on a slice
* @returns a dictionary where keys are iRA0, iRA1, iDEC0, iDEC1
*/
function getBoxCoordinates(){
const naxis1Index = FITS_HEADER.naxis1 - 1;
const naxis2Index = FITS_HEADER.naxis2 - 1;
return {"iRA0": Math.round(naxis1Index / 2 - naxis1Index / 16),
"iRA1": Math.round(naxis1Index / 2 + naxis1Index / 16),
"iDEC0": Math.round(naxis2Index / 2 - naxis2Index / 16),
"iDEC1": Math.round(naxis2Index / 2 + naxis2Index / 16)}
/*return {"iRA0": 0,
"iRA1": naxis1Index,
"iDEC0": 0,
"iDEC1": naxis2Index}*/
}
/**
* Base class for Slice display
* @typedef {Object} Slice
*/
class Slice {
static objectType = "SLICE";
/**
* @constructor
* @param {ViewLinker} viewLinker Object linking spectra and slices
* @param {string} sliceDivId id of the DOM element containing the slice
* @param {string} canvasId id of the canvas
* @param {Array} RADECRangeInDegrees an array containing RA/DEC boundaries of the current cube
* @param {number} width slice width in pixels (int)
* @param {number} height slice height in pixels (int)
*/
constructor(viewLinker, sliceDivId, canvasId, RADECRangeInDegrees, width, height) {
this._im_layer = null;
//this._data_steps = null;
this._viewLinker = viewLinker;
// infoBlock set to null to use Viewer class
var context = {
'file': dataPaths.relFITSFilePath,
/*'url': url,*/
'url': '',
'pixel-slice-range': 0,
'slice-phys-chars': {
'type': FITS_HEADER.ctype3,
'unit': FITS_HEADER.cunit3,
'value': FITS_HEADER.crval3
},
'array-phys-chars': { 'type': FITS_HEADER.btype, 'unit': FITS_HEADER.bunit }
};
// contains infos about selected elements in slice
// not used in 3D at the moment
let infosBlock = new InfosBlock(document.getElementById("infos-line"),
document.getElementsByTagName("body")[0], context);
// object displaying slice
this.viewer = new Viewer(this._viewLinker.relFITSFilePath, width, height, sliceDivId, canvasId,
null, infosBlock);
//container for options buttons in slice
this.customControls = new CustomControls(this.viewer);
this._projection = getProjection(FITS_HEADER.projectionType);
this._hidden_canvas = document.getElementById(canvasId);
this._hidden_canvas.height = height;
this._hidden_canvas.width = width;
this.axesFactory = new AxesFactory(this.viewer, FITS_HEADER.projectionType);
this.axesFactory.build();
this.boxesFactory = null;
this.selector = this.viewer.getSelector();
// end
this._width = width;
this._height = height;
this._RADECRangeInDegrees = RADECRangeInDegrees;
this._minValue = null;
this._maxValue = null;
// standard deviation
this._rms = null;
this._rmsUnit = null;
this._mean = null;
this.map_controls = null;
this._sampButton = null;
//this._initControl();
/*this._absToPix = new AbsToPixelConverter(FITS_HEADER.crpix1, FITS_HEADER.cdelt1,
FITS_HEADER.crpix2, FITS_HEADER.cdelt2,
this._projection);*/
this._shapesLayerGroup = new ol.layer.Group({
layers: []
});
this._shapesLayerGroup.set('title', 'shapes');
this._map = this.viewer.map;
// initial image resolution, for later reset
this._defaultResolution = this._map.getView().getResolution();
// constrols display of markers on open layers image
this._markerManager = new MarkerManager(this._map, true);
// list of markers declared by user
this._markers = [];
// listeners of grid modified event
this.gridEventListeners = [];
this.sliceLoadedlisteners = [];
this.updateSlice = this.updateSlice.bind(this);
this._imageLoadFunction = this._imageLoadFunction.bind(this);
this._getMap = this._getMap.bind(this);
}
changeZoom(level){
//if(Number.isInteger(level)){
const box = getBoxCoordinates();
this._map.getView().setCenter([(box.iRA1+box.iRA0)/2, (box.iDEC1+box.iDEC0)/2]);
this._map.getView().setZoom(this._map.getView().getZoom() + level);
//}
}
addSliceLoadedListener(listener){
this.sliceLoadedlisteners.push(listener);
}
removeSliceLoadedListener(listener){
for(let i=0; i < this.sliceLoadedlisteners.length; i++){
if(this.sliceLoadedlisteners[i] === listener){
this.sliceLoadedlisteners.splice(i, 1);
}
}
}
_executeSliceLoadedListener(event) {
for (let l of this.sliceLoadedlisteners) {
l.sliceLoaded(event);
}
}
get map(){
return this._map;
}
/**
* Add a grid event listener
* @param {object} listener
*/
addGridEventListener(listener) {
this.gridEventListeners.push(listener);
}
/**
* Removes a grid event listener
* @param {object} listener
*/
removeGridEventListener = (listener) => {
for (let i = 0; i < this.gridEventListeners.length; i++) {
if (this.gridEventListeners[i] === listener) {
this.gridEventListeners.splice(i, 1)
}
}
}
/**
* Triggers grid modified event
* @param {object} event
*/
_executeListeners(event) {
for (let l of this.gridEventListeners) {
l.updateGridCall(event);
}
}
/**
* Called when grid modified event has been received
* @param {object} event
*/
updateGridCall(event) {
this.axesFactory.getButtonClick();
}
/**
* Sets the string RMS value with its unit
* @param {number} value (float)
* @param {string} unit
*/
getRms(){
return this._rms;
}
getRmsUnit(){
return this._rmsUnit;
}
setRms(value, unit) {
this._rms = Number.parseFloat(value);
this._rmsUnit = unit;
// writes values in dom
this._setRmsLabel("rms=" + Number.parseFloat(value).toExponential(2) + ' ' + unit);
}
/**
* Sets the string RMS value with its unit
* @param {number} value (float)
* @param {string} unit
*/
setMean(value, unit) {
this._mean = "mean=" + Number.parseFloat(value).toExponential(2) + ' ' + unit;
this._setMeanLabel(this._mean);
}
/**
* Called when markers list triggers an event
*/
markerListClear() {
this._markerManager.clearMarkers();
this._markers = [];
}
/**
* Reset image position and resolution
*/
reset() {
this._map.getView().setCenter(ol.extent.getCenter(this._viewLinker.extent));
this._map.getView().setResolution(this._defaultResolution);
}
/**
* Tests if a pixel has a value, returns false if pixel has no value, i.e RGB value is 255,0,0
* returns true in any other case
* @param {number} x (int)
* @param {number} y (int)
* @returns {boolean}
*/
pixelHasValue(x, y) {
let ctx = this._hidden_canvas.getContext('2d');
let pixelAtPosition = ctx.getImageData(x, this._height - y, 1, 1).data;
if (pixelAtPosition[0] == 255 && pixelAtPosition[1] == 0 && pixelAtPosition[2] == 0) {
return false;
} else {
return true;
}
}
/**
* Toggles display of the samp button on the slice image
* Button will be displayed if state is true, hidden if it is false
* @param {boolean} state
*/
setSampButtonVisible(state) {
if (this._sampButton !== null) {
if (state) {
this._sampButton.element.style.display = "block";
} else {
this._sampButton.element.style.display = "none";
}
}
}
/**
* Called when NED table object triggers an event, adds a marker on the slice pointing on an object in the sky
* @param {Event} event event received from NED table, containing ra/dec and the name of an object
*/
sourceTableCall(event) {
//let res = this._absToPix.convert(degToRad(event.detail.ra), degToRad(event.detail.dec));
let res = this._projection.raDecToiRaiDec(event.detail.ra, event.detail.dec);
try {
this._markerManager.addMarker(res["iRa"], res["iDec"], event.detail["object"].trim());
this._markers.push(new Marker(event.detail.ra, "", event.detail.dec, "", event.detail["object"].trim()));
} catch (error) {
console.log(error);
}
}
/**
* Called when markers list triggers an event
* @param {Event} event event containing a list of markers to plot on the slice
*/
markerListUpdate(event) {
this._markerManager.clearMarkers();
this._markers = [];
for (const marker of event.detail.markers) {
if (marker.ra !== undefined && marker.dec !== undefined && marker.label !== undefined) {
this._markers.push(new Marker(marker.ra, "", marker.dec, "", marker.label));
try {
let iRaiDec = this._projection.raDecToiRaiDec(HMS2DecDeg(marker.ra), DMS2DecDeg(marker.dec));
this._markerManager.addMarker(iRaiDec["iRa"], iRaiDec["iDec"], marker["label"].trim());
} catch (error) {
alert(`Error with object RA : ${marker.ra}, DEC : ${marker.dec}, label : ${marker.label} : ${error} `);
throw error;
}
} else {
alert(`Coordinates are syntaxically incorrect for object RA : ${marker.RA.value}, DEC : ${marker.DEC.value}, label : ${marker.label}`);
throw new Erro("Coordinates are syntaxically incorrect");
}
}
}
/**
* What happens when the image to be displayed in 'image'
* is loaded.
* @param {Image} image an open layer image (ol.Image) that will contain src
* @param {string} src image url
*/
_imageLoadFunction(image, src) {
this.viewer.imageLoadFunction(image, src);
}
/**
* Returns an ol.Graticule open layer object, showing a grid for a coordinate system
* This is an abstract function that must be implemented in a derived class
*/
_getGraticule() {
throw new Error("This method must be implemented");
}
/**
* Returns the open layer map of the slice viewer
* This is an abstract function that must be implemented in a derived class
* @param {number} id of displayed slice in cube (int)
*/
_getMap(sliceDivId) {
throw new Error("This method must be implemented");
}
/**
/**
* Returns the coordinates at cursor position
* @param {Array} olc open layers coordinates
* @returns {string}
*/
coordinateFormat(olc) {
let result;
let ctx = this._hidden_canvas.getContext('2d');
//let pixelAtPosition = ctx.getImageData(olc[0], this._height - olc[1], 1, 1).data;
let raDec = this._projection.iRaiDecToHMSDMS(olc[0], olc[1]);
//console.log(olc);
//if (pixelAtPosition) {
// let data_steps_index = pixelAtPosition.slice(0, 3).join('_');
// if (data_steps_index !== "255_255_255") {
result = "RA=" + raDec['ra'] + ', DEC=' + raDec["dec"] + " ";
result += "x=" + Math.floor(olc[0]) + ', y=' + Math.floor(olc[1]);
// }
//} else {
// result = "???";
//}
return result;
}
/**
* Updates the displayed image. Data object is generally obtained from a request to yafitss
* This is an abstract function that must be implemented in a derived class
* @param {object} data
*/
updateSlice(data) {
throw new Error("This method must be implemented");
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {string} text a function
*/
_setRmsLabel(text) {
throw new Error("This method must be implemented");
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {string} text a function
*/
_setMeanLabel(text) {
throw new Error("This method must be implemented");
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {Event} event event triggered by the click
*/
onclick(event) {
throw new Error("This method must be implemented");
}
_sendSliceWithSamp(){
throw new Error("This method must be implemented");
}
/**
* Creates default buttons on the slice viewer
* @param {object} hidden list of hidden fields
*/
_initCommonControl(hidden) {
let self = this;
// buttons in side bar
var keyCodeProcessor = new KeyCodeProcessor(this.viewer);
keyCodeProcessor.open();
this.customControls.addButton(this.selector.getButtonObject().getButton());
for(let key of this.selector.getButtonObject().getKeyboardMapping()){
keyCodeProcessor.teach(key, () => this.selector.getButtonObject().getButton().click());
}
/*let clickMarkerButton = new ClickMarkerButton(this.viewer);
for(let key of clickMarkerButton.getKeyboardMapping()){
keyCodeProcessor.teach(key, () => clickMarkerButton.getButton().click());
}
this.customControls.addButton(clickMarkerButton.getButton());*/
if(!hidden.includes("boxes")){
this.boxesFactory = new Boxes3DFactory(this.viewer);
this.customControls.addButton(this.boxesFactory.getButtonObject().getButton());
for(let key of this.boxesFactory.getButtonObject().getKeyboardMapping()){
keyCodeProcessor.teach(key, () => this.boxesFactory.getButtonObject().getButton().click());
}
// Pressing del|Suppr => remove the selected feature.
// button is not displayed but key action is registered
let deleteButton = new DeleteButton(this.viewer);
for(let key of deleteButton.getKeyboardMapping()){
keyCodeProcessor.teach(key, () => deleteButton.getButton().click());
}
}
let axesFactory = new AxesFactory(this.viewer, FITS_HEADER.projectionType);
axesFactory.build();
let axesButton = axesFactory.getButtonObject();
//axesButton.getButton().onclick = ()=>{axesFactory.getButtonClick()};
axesButton.setClickAction(()=>{axesFactory.getButtonClick()});
this.customControls.addButton(axesButton.getButton());
axesFactory.getButtonClick()
for(let key of axesButton.getKeyboardMapping()){
keyCodeProcessor.teach(key, () => axesButton.getButton().click());
}
let reset = new ResetButton(this.viewer);
reset.setClickAction(()=>{this.viewer.reset()});
this.customControls.addButton(reset.getButton());
for(let key of reset.getKeyboardMapping()){
keyCodeProcessor.teach(key, () => reset.getButton().click());
}
// Pressing del|backspace => remove the selected feature.
let deleteButton = new DeleteButton(this.viewer);
for(let key of deleteButton.getKeyboardMapping()){
keyCodeProcessor.teach(key, () => { if (this.selector.isOpened()) this.selector.removeSelection() });
}
//this.selector.open();
//return controls;
}
enableSelectorMode(){
this.selector.getButtonObject().getButton().click();
}
/**
* Function to add more buttons to the default ones
* declared in _initControl
* @returns {array}
*/
_initAdditionalControl() {
return [];
}
}
/**
* Class displaying the content of a single slice in an image ( a channel map extracted from a channel (frequency))
* @extends Slice
*/
class SingleSlice extends Slice {
/**
* @constructor
* @param {ViewLinker} viewLinker Object linking spectra and slices
* @param {string} sliceDivId id of the DOM element containing the slice
* @param {string} canvasId id of the canvas
* @param {Array} RADECRangeInDegrees an array containing RA/DEC boundaries of the current cube
* @param {number} width slice width in pixels (int)
* @param {number} height slice height in pixels (int)
*/
constructor(viewLinker, sliceDivId, canvasId, RADECRangeInDegrees, width, height) {
super(viewLinker, sliceDivId, canvasId, RADECRangeInDegrees, width, height);
this.sliceIndex;
this._lastClickMarker = '';
this._initCommonControl(["boxes"]);
this._initAdditionalControl();
this.map_controls = this._getMapControl();
//click in slice viewer
this.viewer.addClickEvent((event) => { this.onclick(event) });
this._getMapControl = this._getMapControl.bind(this);
}
setSliceIndex(sliceIndex) {
this.sliceIndex = sliceIndex;
}
onclick(event) {
if(!this.selector.isOpened()){
//is this test still useful ?
let localName = "";
try{
localName = event.originalEvent.target.localName;
}catch(e){
console.log(e);
}
if(event.originalEvent == null || localName == "canvas"){
// keep old x axis limits before replot
if(this._viewLinker.isRefreshable){
if (this.pixelHasValue(Math.round(event.coordinate[0]), Math.round(event.coordinate[1]))) {
const minVal = this._viewLinker.spectrumViewer.spectrumChart.xAxis[0].min;
const maxVal = this._viewLinker.spectrumViewer.spectrumChart.xAxis[0].max;
this._viewLinker.markLastClickInSlices(event.coordinate);
this._viewLinker.summedSlicesImage.onclick(event);
this._viewLinker.spectrumViewer.setFrequencyMarker(this.sliceIndex);
//DOMAccessor.showLoaderAction(true);
this._viewLinker.spectrumViewer.plot(Math.floor(event.coordinate[0]),
Math.floor(event.coordinate[1]),
() => {
// keep the old min/max values on x axis when
// refreshing graph
// does not seem useful, commented for now
// replot already selected spectral lines
if (this._viewLinker.summedPixelsSpectrumViewer.linePlotter != null) {
this._viewLinker.summedPixelsSpectrumViewer.linePlotter.loadAndPlotLines(
this._viewLinker.summedPixelsSpectrumViewer.linePlotter.obsFreqMin,
this._viewLinker.summedPixelsSpectrumViewer.linePlotter.obsFreqMax,
[this._viewLinker.summedPixelsSpectrumViewer.getSpectrumChartXAxis(),
this._viewLinker.spectrumViewer.getSpectrumChartXAxis()]);
}
// keep current zoom level
this._viewLinker.spectrumViewer.spectrumChart.xAxis[0].setExtremes(minVal, maxVal);
});
}
}else{
alert("Display can not be refreshed. Velocity or redshift value has changed. Please press enter in this field to validate.");
}
}
}
}
/**
* Function to add more buttons to the default ones
* declared in _initControl
*/
_initAdditionalControl() {
const apiQuery = new ServerApi();
// open image in 2D viewer
let view2d = new View2DButton(this.viewer);
view2d.setClickAction(()=>{
//this._sendSliceWithSamp();
let getPath = apiQuery.getFITSSliceImage(this.sliceIndex,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(x => {
let res = JSON.parse(x);
let url = URL_3D_TO_2D + res.result;
window.open(url);
});
});
this.customControls.addButton(view2d.getButton());
//download image
if (yafitsTarget === "OBSPM") {
let saveImage = new SaveImageButton(this.viewer);
saveImage.setClickAction(()=>{
let getPath = apiQuery.getFITSSliceImage(this.sliceIndex,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(x => {
let res = JSON.parse(x);
let url = URL_ROOT + res.result;
window.open(url, '_blank');
});
});
this.customControls.addButton(saveImage.getButton());
}
if (withSAMP) {
let sampButton = new PublishSAMPButton(this.viewer);
//setOnHubAvailability(customControls,sampButton);
sampButton.setClickAction(()=>{
let getPath = apiQuery.getFITSSliceImage(this.sliceIndex,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(x => {
let res = JSON.parse(x);
let url = URL_ROOT + res.result;
let parts = this._viewLinker.relFITSFilePath.split('/');
// sends fits file and png at the same time
sAMPPublisher.sendFitsImageToAll(url, parts[parts.length - 1],
parts[parts.length - 1] + "-" + self.sliceIndex);
sAMPPublisher.sendMarkers(self._markers);
});
sAMPPublisher.sendPNGSlice();
sAMPPublisher.sendMarkers(this._markers)
});
setOnHubAvailabilityButtons(DOMAccessor.get3DSampConnection(), this.customControls, sampButton);
}
}
/**
* Defines the action triggered when the mouse moves on the slice
* This creates a link between the SingleSlice and SummedSlice through the ViewLinker
*/
_getMapControl() {
return [
new ol.control.MousePosition({
className: 'custom-mouse-position',
target: DOMAccessor.getSingleSliceMousePosition(),
undefinedHTML: '',
coordinateFormat: (olc) => { return this.coordinateFormat(olc) }
}),
new ol.control.MousePosition({
className: 'custom-mouse-position',
target: DOMAccessor.getSummedSliceMousePosition(),
undefinedHTML: '',
coordinateFormat: (olc) => { return this._viewLinker.summedSlicesImage.coordinateFormat(olc) }
}),
new ol.control.FullScreen()
]/*.concat(this._initControl()).concat(this._extendControl())*/;
}
/**
* Updates the displayed image. Data object is generally obtained from a request to yafitss
* @param {object} data
*/
updateSlice = (data) => {
/*this._data_steps = data["data_steps"];
if (FITS_HEADER.isSITELLE()) {
for (var k in this._data_steps) {
if (this._data_steps.hasOwnProperty(k)) {
this._data_steps[k] /= Math.abs(3600. * 3600. * FITS_HEADER.cdelt1 * FITS_HEADER.cdelt2);
}
}
}*/
if (this._im_layer) {
this._map.removeLayer(this._im_layer);
}
console.log("### EXTENT");
console.log(this._viewLinker.extent);
this._im_layer = new ol.layer.Image({
source: new ol.source.ImageStatic({
url: URL_ROOT + "/" + data["path_to_png"],
projection: this._viewLinker.olProjection,
imageExtent: this._viewLinker.extent,
imageLoadFunction: this._imageLoadFunction
})
});
this._map.getLayers().insertAt(0, this._im_layer);
// update color bar
DOMAccessor.updateSingleSliceColorBar(URL_ROOT + "/" + data["path_to_legend_png"]);
this.setRms(parseFloat(data["statistics"]["stdev"]), FITS_HEADER.bunit);
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {object} fn a function
*/
_setRmsLabel = (text) => {
DOMAccessor.setSliceRMS(text);
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {object} fn a function
*/
_setMeanLabel = (text) => {
DOMAccessor.setSliceMean(text);
}
}
/**
* Class displaying the content of a map averaged from a frequency-range (velocity-range)
* selected in the averaged spectrum
* @extends Slice
* @property {number} sliceIndex0 selected start index in averaged spectrum (int)
* @property {number} sliceIndex1 selected end index in averaged spectrum (int)
* @property {number} selectedBox box selected by the user in the image
* @property {number} regionOfInterest boundaries of the cube as found in the header
*/
class SummedSlice extends Slice {
/**
* @constructor
*
* @param {ViewLinker} viewLinker Object linking spectra and slices
* @param {string} sliceDivId id of the DOM element containing the slice
* @param {string} canvasId id of the canvas
* @param {Array} RADECRangeInDegrees an array containing RA/DEC boundaries of the current cube
* @param {number} width slice width in pixels (int)
* @param {number} height slice height in pixels (int)
*/
constructor(viewLinker, sliceDivId, canvasId, RADECRangeInDegrees, width, height) {
super(viewLinker, sliceDivId, canvasId, RADECRangeInDegrees, width, height);
// public attributes
this.sliceIndex0 = null;
this.slideIndex1 = null;
this.selectedBox = null;
this.regionOfInterest = {
iRA0: 0,
iRA1: FITS_HEADER.naxis1 - 1,
iDEC0: 0,
iDEC1: FITS_HEADER.naxis2 - 1,
/*iFREQ0: Math.round(FITS_HEADER.naxis3/2) - Math.round(FITS_HEADER.naxis3/8) ,
iFREQ1: Math.round(FITS_HEADER.naxis3/2) + Math.round(FITS_HEADER.naxis3/8) ,*/
iFREQ0: 0,
iFREQ1: FITS_HEADER.naxis3 - 1
};
// last position clicked in the image is initialized at initial pixel value
const initialPixel = FITS_HEADER.getCentralPixelPosition();
this._lastClick = {
iRA: initialPixel[0],
iDEC: initialPixel[1]
}
// Here we have all the stuff to create boxes on summedslices
// and trigger the update of the spectrum of sums of pixels per slice
this._boxSource = new ol.source.Vector({
wrapX: false
});
this._boxLayer = new ol.layer.Vector({
source: this._boxSource
});
this._polygon = null;
//private attributes
this._select = this.getSelect();
this._dragBox = this._getDragBox();
this._initCommonControl([]);
this._initAdditionalControl();
this.map_controls = this._getMapControl();
this.selectAction = (feature)=>{
// this.selectedBox = e.selected[0];
this.selectedBox = feature;
var extent = feature.getGeometry().getExtent();
// keep current limits for x axis when refreshing the plot
this._viewLinker.summedPixelsSpectrumViewer.replot(extent[0], extent[2], extent[1], extent[3]);
this.regionOfInterest.iRA0 = Math.round(extent[0]);
this.regionOfInterest.iRA1 = Math.round(extent[2]);
this.regionOfInterest.iDEC0 = Math.round(extent[1]);
this.regionOfInterest.iDEC1 = Math.round(extent[3]);
}
this.boxesFactory.setSelectAction(this.selectAction);
this._map.addInteraction(this._select);
//click in summed slice viewer
this.viewer.addClickEvent((event) => { this.onclick(event) });
this._imageLoadFunction = this._imageLoadFunction.bind(this);
this.onclick = this.onclick.bind(this);
this.getSelect = this.getSelect.bind(this);
this._getDragBox = this._getDragBox.bind(this);
this._getMapControl = this._getMapControl.bind(this);
this.forgetSelectedBox = this.forgetSelectedBox.bind(this);
}
get boxSource() {
return this._boxSource;
}
_sendSliceWithSamp(){
const apiQuery = new ServerApi();
let getPath = apiQuery.getSummedFITSSliceImage( this.sliceIndex0,
this.sliceIndex1,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(x => {
let res = JSON.parse(x);
let url = URL_ROOT + res.result;
let parts = this._viewLinker.relFITSFilePath.split('/');
// sends fits image
sAMPPublisher.sendFitsImageToAll(url, parts[parts.length - 1], parts[parts.length - 1] + "-" + self.sliceIndex);
sAMPPublisher.sendMarkers(self._markers);
});
sAMPPublisher.sendPNGSummedSlices();
sAMPPublisher.sendMarkers(this._markers);
}
drawInitialBox(){
const box = getBoxCoordinates();
/*const iRA0 = 0;
const iRA1 = naxis1Index;
const iDEC0 =0;
const iDEC1 =naxis2Index;*/
const coords = [box.iRA0, box.iRA1, box.iDEC0, box.iDEC1];
this.boxesFactory.addBox(coords[0],coords[1],coords[2],coords[3]);
let pf = this.boxesFactory.source.getFeatures()[0];
this._polygon = pf.getGeometry();
let select = this.getSelect();
this.map.addInteraction(select);
select.getFeatures().push(pf);
select.dispatchEvent({
type: 'select',
selected: [pf],
deselected: []
});
}
/**
* Function to add more buttons to the default ones
* declared in _initControl
*/
_initAdditionalControl() {
const apiQuery = new ServerApi();
// open image in 2D viewer
let view2d = new View2DButton(this.viewer);
view2d.setClickAction(()=>{
let getPath = apiQuery.getSummedFITSSliceImage(this.sliceIndex0,
this.sliceIndex1,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(x => {
let res = JSON.parse(x);
let url = URL_3D_TO_2D + res.result;
window.open(url);
});
});
this.customControls.addButton(view2d.getButton());
//download image
if (yafitsTarget === "OBSPM") {
let saveImage = new SaveImageButton(this.viewer);
saveImage.setClickAction(()=>{
var getPath = apiQuery.getSummedFITSSliceImage( this.sliceIndex0,
this.sliceIndex1,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(result => {
var res = JSON.parse(result);
var url = URL_ROOT + res.result;
window.open(url, '_blank');;
});
});
this.customControls.addButton(saveImage.getButton());
}
if (withSAMP) {
let sampButton = new PublishSAMPButton(this.viewer);
//setOnHubAvailability(customControls,sampButton);
sampButton.setClickAction(()=>{
let getPath = apiQuery.getSummedFITSSliceImage( this.sliceIndex0,
this.sliceIndex1,
this._viewLinker.relFITSFilePath,
(resp)=>{});
getPath.then(x => {
let res = JSON.parse(x);
let url = URL_ROOT + res.result;
let parts = this._viewLinker.relFITSFilePath.split('/');
// sends fits image
sAMPPublisher.sendFitsImageToAll(url, parts[parts.length - 1], parts[parts.length - 1] + "-" + self.sliceIndex);
sAMPPublisher.sendMarkers(self._markers);
});
sAMPPublisher.sendPNGSummedSlices();
sAMPPublisher.sendMarkers(this._markers)
});
setOnHubAvailabilityButtons(DOMAccessor.get3DSampConnection(), this.customControls, sampButton);
}
}
onclick(event) {
// true if event is a click from another map object (i.e the other slice)
let singleSliceClick = false;
if(event.type==="click" && this.viewer.map.ol_uid !== event.target.ol_uid ){
singleSliceClick = true;;
}
// the selector object is closed or user clicked on a pixel in the other slice
if(!this.selector.isOpened() || singleSliceClick === true ){
if(event.originalEvent == null || event.originalEvent.target.localName == "canvas"){
if(this._viewLinker.isRefreshable){
if (event.coordinate !== undefined) {
this._lastClick.iRA = Math.round(event.coordinate[0]);
this._lastClick.iDEC = Math.round(event.coordinate[1]);
}
let apiQuery = new ServerApi();
apiQuery.getSummedSliceValueAtPixel(
this._lastClick.iRA, this._lastClick.iDEC,
this.regionOfInterest.iFREQ0, this.regionOfInterest.iFREQ1,
this._viewLinker.relFITSFilePath,
(resp)=>{
// keep old x axis limits before replot
const minVal = this._viewLinker.spectrumViewer.spectrumChart.xAxis[0].min;
const maxVal = this._viewLinker.spectrumViewer.spectrumChart.xAxis[0].max;
let result = resp["data"];
const pixelValue = result["result"][0][0];
if (result["status"] && pixelValue !== null){
this._viewLinker.spectrumViewer.setFrequencyMarker(this._viewLinker.getSliceIndex());
//let self = this;
this._viewLinker.spectrumViewer.plot(this._lastClick.iRA,
this._lastClick.iDEC/*, () => {
// set old x axis limits
self._viewLinker.spectrumViewer.spectrumChart.xAxis[0].setExtremes(minVal, maxVal);
}*/);
if (event.coordinate !== undefined)
this._viewLinker.markLastClickInSlices(event.coordinate);
// event is only a value refresh request, i.e. it comes from an interval selection on the spectrum
if (event.type === EventFactory.EVENT_TYPES.refreshFrequency) {
this._viewLinker.setFluxDensityInSummedPopup(pixelValue, true);
}
// other event, i.e. a click on the image
else {
this._viewLinker.setFluxDensityInSummedPopup(pixelValue, false);
}
}
});
}else{
alert("Display can not be refreshed. Velocity or redshift value has changed. Please press enter in this field to validate.");
}
}
}
}
/**
* Adds a Layer the shapesLayerGroup
* @param {Layer} layer an open layer Layer object (ol.layer.Layer)
*/
addShapesLayer(layer) {
this._shapesLayerGroup.getLayers().getArray().push(layer);
}
/**
* Defines the action triggered when the mouse moves on the slice
* This creates a link between the SingleSlice and SummedSlice through the ViewLinker
*/
_getMapControl() {
return [
new ol.control.MousePosition({
className: 'custom-mouse-position',
target: DOMAccessor.getSingleSliceMousePosition(),
undefinedHTML: '',
coordinateFormat: (olc) => { return this._viewLinker.singleSliceImage.coordinateFormat(olc) }
}),
new ol.control.MousePosition({
className: 'custom-mouse-position',
target: DOMAccessor.getSummedSliceMousePosition(),
undefinedHTML: '',
coordinateFormat: (olc) => { return this.coordinateFormat(olc) }
}),
new ol.control.FullScreen()
];
}
/**
* Returns the open layer map of the slice viewer
* @param {number} id of displayed slice in cube (int)
*/
_getMap(sliceDivId) {
return new ol.Map({
target: sliceDivId,
view: new ol.View({
projection: this._viewLinker.olProjection,
center: ol.extent.getCenter(this._viewLinker.extent),
resolution: this._hidden_canvas.width / 512
}),
controls: this.controls,
layers: [this._shapesLayerGroup]
});
}
/**
* Returns a ol.interaction.Select object, defining what happens when user clicks on a box in the slice
* @returns {Select} ol.interaction.Select
*/
getSelect() {
let select = new ol.interaction.Select({
condition: ol.events.pointerMove,
});
select.on('select', (e) => {
if (e.selected.length) {
for(let i=0; i<this._boxSource.getFeatures().length; i++){
if(this._boxSource.getFeatures()[i] !== e.selected[0]){
this._boxSource.getFeatures()[i].setStyle(standardFeature(this._boxSource.getFeatures()[i]));
}else{
this._boxSource.getFeatures()[i].setStyle(highlightedFeature(this._boxSource.getFeatures()[i]));
}
}
this.selectedBox = e.selected[0];
var extent = e.selected[0].getGeometry().getExtent();
// keep current limits for x axis when refreshing the plot
this._viewLinker.summedPixelsSpectrumViewer.replot(extent[0], extent[2], extent[1], extent[3]);
this.regionOfInterest.iRA0 = Math.round(extent[0]);
this.regionOfInterest.iRA1 = Math.round(extent[2]);
this.regionOfInterest.iDEC0 = Math.round(extent[1]);
this.regionOfInterest.iDEC1 = Math.round(extent[3]);
}
console.log(e);
});
return select;
}
/**
* Returns a DragBox object, defining what happens when user creates a box in the slice
* @returns {DragBox} ol.interaction.DragBox
*/
_getDragBox() {
let interaction = new ol.interaction.Select({ condition: ol.events.click, hitTolerance: 2 });
const translate = new ol.interaction.Translate({
features: interaction.getFeatures(),
});
this.viewer.map.addInteraction(translate);
let dragBox = new ol.interaction.DragBox();
let self = this;
dragBox.on('boxend', (e) => {
var extent = this._dragBox.getGeometry().getExtent();
var tl = ol.extent.getTopLeft(extent);
var tr = ol.extent.getTopRight(extent);
var br = ol.extent.getBottomRight(extent);
var bl = ol.extent.getBottomLeft(extent);
if (this.pixelHasValue(tl[0], tl[1]) &&
this.pixelHasValue(tr[0], tr[1]) &&
this.pixelHasValue(br[0], br[1]) &&
this.pixelHasValue(bl[0], bl[1])
) {
var corners = []
corners.push(tl, tr, br, bl, tl);
var pf = new ol.Feature({
geometry: new ol.geom.Polygon([corners])
});
pf.setStyle(standardFeature(pf));
this._boxSource.addFeature(pf);
let select = new ol.interaction.Select();
let translate = new ol.interaction.Translate({
features : pf
});
this._map.addInteraction(translate);
this._map.addInteraction(select);
} else {
alert("At least one of selected points have no value.");
}
});
dragBox.on('boxdrag', () => {
});
return dragBox;
}
/**
* Updates the displayed image. Data object is generally obtained from a request to yafitss
* This is an abstract function that must be implemented in a derived class
* @param {object} data
*/
updateSlice = (data) => {
//this._data_steps = data["data_steps"];
let path_to_png = data["path_to_png"];
if (this._im_layer) {
this._map.removeLayer(this._im_layer);
this._map.removeLayer(this._boxLayer);
}
this._im_layer = new ol.layer.Image({
source: new ol.source.ImageStatic({
url: URL_ROOT + "/" + path_to_png,
projection: this._viewLinker.olProjection,
imageExtent: this._viewLinker.extent,
imageLoadFunction: this._imageLoadFunction
})
});
this._map.getLayers().insertAt(0, this._im_layer);
this._map.getLayers().insertAt(1, this._boxLayer);
// update color bar
DOMAccessor.updateSingleSummedColorBar(URL_ROOT + "/" + data["path_to_legend_png"]);
this.setRms(parseFloat(data["statistics"]["stdev"]), FITS_HEADER.bunit);
}
/**
* Deletes the currently selected box, if it exists
*/
forgetSelectedBox() {
console.log('this.forgetSelectedBox = function() {: entering');
var styleForget = function () {
return [new ol.style.Style({
stroke: new ol.style.Stroke({
color: [255, 0, 0, 1]
})
})];
};
/*if (this.selectedBox) {
this.selectedBox.setStyle(styleForget);
this._boxSource.removeFeature(this.selectedBox);
this._boxSource.refresh();
this._viewLinker.summedPixelsSpectrumViewer.replot(0, this._width - 1, 0, this._height - 1)
this.regionOfInterest.iRA0 = Math.round(0);
this.regionOfInterest.iRA1 = Math.round(this._width - 1);
this.regionOfInterest.iDEC0 = Math.round(0);
this.regionOfInterest.iDEC1 = Math.round(this._height - 1);
}*/
console.log('this.forgetSelectedBox = function() {: exiting');
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {object} fn a function
*/
_setRmsLabel = (text) => {
DOMAccessor.setSummedSliceRMS(text);
}
/**
*
* This is an abstract function that must be implemented in a derived class
* @param {object} fn a function
*/
_setMeanLabel = (text) => {
DOMAccessor.setSummedSliceMean(text);
}
}
export {
Slice, SingleSlice, SummedSlice, getSliceUpdateEvent
}