/*
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
*/
import {get, isEmpty, isArray} from 'lodash';
import {makeWisePlotRequest} from './WiseRequestList.js';
import {make2MassPlotRequest} from './TwoMassRequestList.js';
import {makeAtlasPlotRequest} from './AtlasRequestList.js';
import {makeLsstSdssPlotRequest, makeLsstWisePlotRequest} from './LsstSdssRequestList.js';
import {WebPlotRequest, TitleOptions} from '../visualize/WebPlotRequest.js';
import {ZoomType} from '../visualize/ZoomType.js';
import {Band} from '../visualize/Band';
import {getCellValue} from '../tables/TableUtil.js';
import {makeWorldPt, parseWorldPt} from '../visualize/Point.js';
import {MetaConst} from '../data/MetaConst.js';
import {CoordinateSys} from '../visualize/CoordSys.js';
import {hasObsCoreLikeDataProducts} from '../util/VOAnalyzer.js';
import {dispatchUpdateCustom} from '../visualize/MultiViewCntlr.js';
import {makeObsCoreConverter, getObsCoreSingleDataProduct, getObsCoreGridDataProduct} from './ObsCoreConverter.js';
import {
createGridImagesActivate,
createRelatedDataGridActivate,
createSingleImageActivate
} from './ImageDataProductsUtil';
const FILE= 'FILE';
function matchById(table,id) {
if (!id) return false;
const value= findTableMetaEntry(table, [MetaConst.IMAGE_SOURCE_ID, MetaConst.DATASET_CONVERTER]);
if (!value) return false;
return value.toUpperCase()===id.toUpperCase();
}
/**
* @param {TableModel} table
* @param {DataProductsConvertType} converterTemplate
* @return {DataProductsConvertType}
*/
const simpleCreate= (table, converterTemplate) => converterTemplate;
/**
* @global
* @public
* @typedef {Object} DataProductsDisplayType
* @prop {string} displayType
* @prop {Function} activate
* @prop {Object} menu
*
*/
/**
* @global
* @public
* @typedef {Object} ActivateParams
* @prop {string} imageViewerId
* @prop {string} chartViewerId
* @prop {string} tableGroupViewerId
* @prop {string} converterId
*
*/
/**
* Returns a callback function or a Promise<DataProductsDisplayType>.
* callback: function(table:TableModel, row:String,activeParams:{imageViewerId:String,chartViewId:String,tableViewId:String,converterId:String})
* @param makeReq
* @return {function | promise}
*/
function getSingleDataProductWrapper(makeReq) {
return (table, row, activateParams) => {
const {imageViewerId, converterId}= activateParams;
const retVal= makeReq(table, row, true);
const r= get(retVal,'single');
const activate= createSingleImageActivate(r,imageViewerId,converterId,table.tbl_id,row);
return Promise.resolve( { displayType:'images', activate, menu:undefined });
};
}
/**
*
* Returns a callback function or a Promise<DataProductsDisplayType>.
* callback: function(table:TableModel, plotRows:Array.<Object>,activeParams:{imageViewerId:String,chartViewId:String,tableViewId:String,converterId:String})
* @param makeReq
* @return {function | promise}
*/
function getGridDataProductWrapper(makeReq) {
return (table, plotRows, activateParams) => {
const {imageViewerId, converterId}= activateParams;
const reqAry= plotRows.map( (pR) => makeReq(table,pR.row,true))
.filter( (r) => (r && r.single))
.map( (result) => result.single);
const activate= createGridImagesActivate(reqAry,imageViewerId,converterId,table.tbl_id,plotRows);
return Promise.resolve( { displayType:'images', activate, menu:undefined });
};
}
/**
*
* @param makeReq
* @return {Function}
*/
function getRelatedDataProductWrapper(makeReq) {
return (table, row, threeColorOps, highlightPlotId, activateParams) => {
const {imageViewerId, converterId}= activateParams;
const retVal= makeReq(table, row, false,true,threeColorOps);
if (retVal) {
const activate= createRelatedDataGridActivate(retVal,imageViewerId,converterId,table.tbl_id, highlightPlotId);
return Promise.resolve( { displayType:'images', activate, menu:undefined });
}
else {
return Promise.resolve( {});
}
};
}
/**
* @global
* @public
* @typedef {Object} DataProductsConvertType
*
* @prop {function} tableMatches, function to test if the table fits the template form: tableMatches(TableModel): boolean
* @prop {function(table:TableModel, template:DataProductsConvertType):DataProductsConvertType} create - create the converter based on this template. The converter is created by a function that is
* passed a table and the converter template. It then may return a new converter template
* that could be optionally customized to the table.
* form: create(TableModel,DataProductsConvertType): DataProductsConvertType
* @prop {boolean} threeColor supports three color images
* @prop {boolean} hasRelatedBands supports groups of related images
* @prop {boolean} canGrid support grids of images
* @prop {number} maxPlot total number of images that can be created at a time, i.e. page size
*
* @prop {function(table:TableModel,row:String,activateParams:ActivateParams):function} getSingleDataProduct
* pass table,rowNum,activate params return a activate function
* required
*
* @prop {function(table:TableModel,plotRows:Array<{plotId:String,row:number,highlight:boolean}>,activateParams:ActivateParams):function} getGridDataProduct
* pass(table, array of plotrows, and activateParams) return activate function.
* Only required if canGrid is true
*
* @prop {function(table:TableModel,plotRows:Array<Object>,threeColorOps:Object, highlightPlotId:string,activateParams:ActivateParams):function}
* getRelatedDataProduct, pass (table, row, threeColorOps, highlightPlotId, activateParams) , return activate function.
* Only required if hasRelatedBands is true
* @prop {Object} threeColorBands definition of the three color plot request
*
*/
/**
* @type {Array.<DataProductsConvertType>}
*/
export const converterTemplates = [
{
converterId : 'wise',
tableMatches: (table) => matchById(table,'wise'),
create : simpleCreate,
threeColor : true,
hasRelatedBands : true,
canGrid : true,
maxPlots : 12,
getSingleDataProduct: getSingleDataProductWrapper(makeWisePlotRequest),
getGridDataProduct: getGridDataProductWrapper(makeWisePlotRequest),
getRelatedDataProduct: getRelatedDataProductWrapper(makeWisePlotRequest),
threeColorBands : {
b1 : {color : Band.RED, title: 'Band 1'},
b2 : {color : Band.GREEN, title: 'Band 2'},
b3 : {color : null, title: 'Band 3'},
b4 : {color : Band.BLUE, title: 'Band 4'}
},
},
{
converterId : 'atlas',
tableMatches: (table) => matchById(table,'atlas'),
create : simpleCreate,
hasRelatedBands : true,
canGrid : true,
maxPlots : 5,
getSingleDataProduct: getSingleDataProductWrapper(makeAtlasPlotRequest),
getGridDataProduct: getGridDataProductWrapper(makeAtlasPlotRequest),
getRelatedDataProduct: getRelatedDataProductWrapper(makeAtlasPlotRequest),
},
{
converterId : 'twomass',
tableMatches: (table) => matchById(table,'twomass'),
create : simpleCreate,
threeColor : true,
hasRelatedBands : true,
canGrid : true,
maxPlots : 12,
getSingleDataProduct: getSingleDataProductWrapper(make2MassPlotRequest),
getGridDataProduct: getGridDataProductWrapper(make2MassPlotRequest),
getRelatedDataProduct: getRelatedDataProductWrapper(make2MassPlotRequest),
threeColorBands : {
J : {color : Band.RED, title: 'J'},
H : {color : Band.GREEN, title: 'H'},
K : {color : Band.BLUE, title: 'K'}
}
},
{
converterId : 'lsst_sdss',
tableMatches: (table) => matchById(table,'lsst_sdss'),
create : simpleCreate,
threeColor : true,
hasRelatedBands : true,
canGrid : true,
maxPlots : 12,
getSingleDataProduct: getSingleDataProductWrapper(makeLsstSdssPlotRequest),
getGridDataProduct: getGridDataProductWrapper(makeLsstSdssPlotRequest),
getRelatedDataProduct: getRelatedDataProductWrapper(makeLsstSdssPlotRequest),
threeColorBands : {
u : {color : null, title: 'u'},
g : {color : Band.RED, title: 'g'},
r : {color : Band.GREEN, title: 'r'},
i : {color : null, title: 'i'},
z : {color : Band.BLUE, title: 'z'}
}
},
{
converterId : 'lsst_wise',
tableMatches: (table) => matchById(table,'lsst_wise'),
create : simpleCreate,
threeColor : true,
hasRelatedBands : true,
canGrid : true,
maxPlots : 12,
getSingleDataProduct: getSingleDataProductWrapper(makeLsstWisePlotRequest),
getGridDataProduct: getGridDataProductWrapper(makeLsstWisePlotRequest),
getRelatedDataProduct: getRelatedDataProductWrapper(makeLsstWisePlotRequest),
threeColorBands : {
b1 : {color : Band.RED, title: 'Band 1'},
b2 : {color : Band.GREEN, title: 'Band 2'},
b3 : {color : null, title: 'Band 3'},
b4 : {color : Band.BLUE, title: 'Band 4'}
}
},
{
converterId : 'ObsCore',
tableMatches: hasObsCoreLikeDataProducts,
create : makeObsCoreConverter,
threeColor : false,
hasRelatedBands : false,
canGrid : true,
maxPlots : 8,
getSingleDataProduct: getObsCoreSingleDataProduct,
getGridDataProduct: getObsCoreGridDataProduct,
getRelatedDataProduct: () => Promise.reject('related data products not supported')
},
{
converterId : 'UNKNOWN',
tableMatches: (table) => !isEmpty(Object.keys(findADataSourceColumn(table.tableMeta,table.tableData.columns))),
create : simpleCreate,
threeColor : false,
hasRelatedBands : false,
canGrid : true,
maxPlots : 12,
getSingleDataProduct: getSingleDataProductWrapper(makeRequestForUnknown),
getGridDataProduct: () => Promise.reject('grid not supported'),
getRelatedDataProduct: () => Promise.reject('related data products not supported')
},
{
converterId : 'SimpleMoving',
tableMatches: () => false,
create : simpleCreate,
threeColor : false,
hasRelatedBands : false,
canGrid : false,
maxPlots : 12,
getSingleDataProduct: getSingleDataProductWrapper(makeRequestSimpleMoving),
getGridDataProduct: () => Promise.reject('grid not supported'),
getRelatedDataProduct: () => Promise.reject('related data products not supported')
}
];
export function initImage3ColorDisplayManagement(viewerId) {
const customEntry= converterTemplates.reduce( (newObj, template) => {
if (!template.threeColor) return newObj;
newObj[template.converterId]= {...template.threeColorBands, threeColorVisible:false};
return newObj;
}, {});
dispatchUpdateCustom(viewerId, customEntry);
}
/**
*
* @param {TableModel} table
* @return {DataProductsConvertType}
*/
export function defaultMakeDataProductsConverter(table) {
const t= converterTemplates.find( (template) => template.tableMatches(table) );
return t && t.create(table,t);
}
/**
*
* @param table
* @param {TableModel} table
* @return {DataProductsConvertType}
*/
let overrideMakeDataProductsConverter;
/**
* get a convert factory for a table
* @param {TableModel} table
* @return {DataProductsConvertType}
*/
export const makeDataProductsConverter= (table) =>
(overrideMakeDataProductsConverter && overrideMakeDataProductsConverter(table)) || defaultMakeDataProductsConverter(table);
export const setOverrideDataProductsConverterFactory = (f) => overrideMakeDataProductsConverter= f;
export function addTemplate(template) { converterTemplates.unshift(template); }
export function addTemplateToEnd(template) { converterTemplates.push(template); }
/**
* Support data the we don't know about
* @param table
* @param row
* @param includeSingle
* @param includeStandard
* @return {{}}
*/
function makeRequestForUnknown(table, row, includeSingle, includeStandard) {
const {tableMeta:meta}= table;
const dataSource= findADataSourceColumn(meta,table.tableData.columns);
if (!dataSource) return {};
let positionWP= null;
let sAry= meta[MetaConst.POSITION_COORD_COLS] && meta[MetaConst.POSITION_COORD_COLS].split(';');
if (!sAry) sAry= meta[MetaConst.CENTER_COLUMN] && meta[MetaConst.CENTER_COLUMN].split(';');
if (!isEmpty(sAry)) {
const lon= Number(getCellValue(table,row,sAry[0]));
const lat= Number(getCellValue(table,row,sAry[1]));
const csys= CoordinateSys.parse(sAry[2]);
positionWP= makeWorldPt(lon,lat,csys);
}
else if (meta[MetaConst.POSITION_COORD]) {
positionWP= parseWorldPt(meta[MetaConst.POSITION_COORD]);
}
const retval= {};
if (includeSingle) {
retval.single= makeRequest(table,dataSource.name,positionWP, row);
}
if (includeStandard) {
retval.standard= [makeRequest(table,dataSource.name,positionWP, row)];
retval.highlightPlotId= retval.standard[0].getPlotId();
}
return retval;
}
function makeRequestSimpleMoving(table, row, includeSingle, includeStandard) {
const {tableMeta:meta, tableData}= table;
const dataSource= findADataSourceColumn(meta, tableData.columns);
if (!dataSource) return {};
const sAry= meta[MetaConst.POSITION_COORD_COLS].split(';');
if (!sAry || sAry.length!== 3) return [];
let positionWP= null;
if (!isEmpty(sAry)) {
const lon= Number(getCellValue(table,row,sAry[0]));
const lat= Number(getCellValue(table,row,sAry[1]));
const csys= CoordinateSys.parse(sAry[2]);
positionWP= makeWorldPt(lon,lat,csys);
}
const retval= {};
if (includeSingle) {
retval.single= makeMovingRequest(table,row,dataSource.name,positionWP,'simple-moving-single-'+(row %24));
}
if (includeStandard) {
retval.standard= [makeMovingRequest(table,row,dataSource.name,positionWP,'simple-moving-single')];
retval.highlightPlotId= retval.standard[0].getPlotId();
}
return retval;
}
const defDataSourceGuesses= [ 'FILE', 'FITS', 'DATA', 'SOURCE', 'URL' ];
const dataSourceUpper= MetaConst.DATA_SOURCE.toUpperCase();
function findADataSourceColumn(meta,columns) {
const dsCol= Object.keys(meta).find( (key) => key.toUpperCase()===dataSourceUpper);
let guesses= meta[dsCol] ? [meta[dsCol],...defDataSourceGuesses] : defDataSourceGuesses;
guesses= guesses.map( (g) => g.toUpperCase());
return columns.find( (c) => guesses.includes(c.name.toUpperCase()));
}
function findTableMetaEntry(table,ids) {
const testIdAry= isArray(ids) ? ids.map( (id) => id.toUpperCase()) : [ids.toUpperCase()];
const foundKey= Object.keys(table.tableMeta)
.find( (key) => testIdAry
.find( (t) => t===key.toUpperCase()));
return foundKey ? table.tableMeta[foundKey] : undefined;
}
/**
*
* @param table
* @param row
* @param dataSource
* @param positionWP
* @param plotId
* @return {*}
*/
function makeMovingRequest(table, row, dataSource, positionWP, plotId) {
const url= getCellValue(table,row,dataSource);
const r = WebPlotRequest.makeURLPlotRequest(url, 'Fits Image');
r.setTitleOptions(TitleOptions.FILE_NAME);
r.setZoomType(ZoomType.TO_WIDTH_HEIGHT);
r.setPlotId(plotId);
r.setOverlayPosition(positionWP);
return r;
}
/**
*
* @param table
* @param dataSource
* @param positionWP
* @param row
* @return {*}
*/
function makeRequest(table, dataSource, positionWP, row) {
if (!table || !dataSource) return null;
let r;
const source= getCellValue(table, row, dataSource);
if (dataSource.toLocaleUpperCase() === FILE) {
r = WebPlotRequest.makeFilePlotRequest(source, 'Fits Image');
}
else {
r = WebPlotRequest.makeURLPlotRequest(source, 'Fits Image');
}
r.setZoomType(ZoomType.FULL_SCREEN);
r.setTitleOptions(TitleOptions.FILE_NAME);
r.setPlotId(source);
if (positionWP) r.setOverlayPosition(positionWP);
return r;
}