/*
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
*/
import Enum from 'enum';
import {get,isEmpty,isObject, flattenDeep, values, isUndefined} from 'lodash';
import {WebPlotRequest, TitleOptions} from '../WebPlotRequest.js';
import {TABLE_LOADED, TABLE_SELECT,TABLE_HIGHLIGHT,TABLE_UPDATE,
TABLE_REMOVE, TBL_RESULTS_ACTIVE} from '../../tables/TablesCntlr.js';
import ImagePlotCntlr, {visRoot, dispatchDeletePlotView, dispatchPlotImageOrHiPS} from '../ImagePlotCntlr.js';
import {primePlot, getDrawLayerById} from '../PlotViewUtil.js';
import {REINIT_APP} from '../../core/AppDataCntlr.js';
import {doFetchTable, getTblById, getActiveTableId, getTableInGroup, isTableUsingRadians} from '../../tables/TableUtil.js';
import {cloneRequest, makeTableFunctionRequest, MAX_ROW } from '../../tables/TableRequestUtil.js';
import MultiViewCntlr, {getMultiViewRoot, getViewer} from '../MultiViewCntlr.js';
import {serializeDecimateInfo} from '../../tables/Decimate.js';
import {DrawSymbol} from '../draw/PointDataObj.js';
import {computeCentralPtRadiusAverage, toDegrees} from '../VisUtil.js';
import {makeWorldPt, pointEquals} from '../Point.js';
import {logError} from '../../util/WebUtil.js';
import {getCornersColumns} from '../../tables/TableInfoUtil.js';
import {dispatchCreateDrawLayer,dispatchDestroyDrawLayer, dispatchModifyCustomField,
dispatchAttachLayerToPlot, getDlAry} from '../DrawLayerCntlr.js';
import Catalog from '../../drawingLayers/Catalog.js';
import {TableSelectOptions} from '../../drawingLayers/CatalogUI.jsx';
import {getNextColor} from '../draw/DrawingDef.js';
import {dispatchAddTableTypeWatcherDef} from '../../core/MasterSaga';
import {findTableCenterColumns, isCatalog, isTableWithRegion, hasCoverageData, findTableRegionColumn} from '../../util/VOAnalyzer.js';
import {parseObsCoreRegion} from '../../util/ObsCoreSRegionParser.js';
import {getAppOptions} from '../../core/AppDataCntlr';
import {getSearchTarget} from './CatalogWatcher';
import {MetaConst} from '../../data/MetaConst.js';
import {getPlotViewById} from '../PlotViewUtil';
import {isHiPS} from '../WebPlot';
export const CoverageType = new Enum(['X', 'BOX', 'REGION', 'ALL', 'GUESS']);
export const FitType= new Enum (['WIDTH', 'WIDTH_HEIGHT']);
const COVERAGE_TARGET = 'COVERAGE_TARGET';
const COVERAGE_RADIUS = 'COVERAGE_RADIUS';
const COVERAGE_TABLE = 'COVERAGE_TABLE';
export const COVERAGE_CREATED = 'COVERAGE_CREATED';
export const PLOT_ID= 'CoveragePlot';
/**
* @global
* @public
* @typedef {Object} CoverageOptions
* @summary options of coverage
*
* @prop {string} title
* @prop {string} tip
* @prop {string} coverageType - one of 'GUESS', 'BOX', 'REGION', 'ALL', or 'X' default is 'ALL'
* @prop {string} overlayPosition search position point to overlay, e.g '149.08;68.739;EQ_J2000'
* @prop {string|Object.<String,String>} symbol - symbol name one of 'X','SQUARE','CROSS','DIAMOND','DOT','CIRCLE','BOXCIRCLE', 'ARROW'
* @prop {string|Object.<String,Number>} symbolSize - a number of the symbol size or an object keyed by table id and value the symbol size
* @prop {string|Object.<String,String>} color - a color the symbol size or an object keyed by table id and color
* @prop {number} fovMaxFitsSize how big this fits image can be (in degrees)
* @prop {number} fovDegMinSize - minimum field of view size in degrees
* @prop {number} fovDegFallOver - the field of view size to determine when to move between and HiPS and an image
* @prop {boolean} multiCoverage - overlay more than one table on the coverage
* @prop {string} gridOn - one of 'FALSE','TRUE','TRUE_LABELS_FALSE'
*/
const defOptions= {
title: '2MASS K_s',
tip: 'Coverage',
getCoverageBaseTitle : (table) => '', // eslint-disable-line no-unused-vars
coverageType : CoverageType.ALL,
symbol : DrawSymbol.SQUARE,
symbolSize : 5,
overlayPosition: undefined,
color : null,
highlightedColor : 'blue',
multiCoverage : true, // this is not longer supported, we now always do multi coverage
gridOn : false,
useBlankPlot : false,
fitType : FitType.WIDTH_HEIGHT,
ignoreCatalogs:false,
useHiPS: true,
hipsSourceURL : 'ivo://CDS/P/2MASS/color', // url
imageSourceParams: {
Service : 'TWOMASS',
SurveyKey: 'asky',
SurveyKeyBand: 'k',
title : '2MASS K_s'
},
fovDegFallOver: .13,
fovMaxFitsSize: .2,
autoConvertOnZoom: false,
fovDegMinSize: 100/3600, //defaults to 100 arcsec
viewerId:'DefCoverageId',
paused: true
};
const overlayCoverageDrawing= makeOverlayCoverageDrawing();
export function startCoverageWatcher(options) {
dispatchAddTableTypeWatcherDef( { ...coverageWatcherDef, options });
}
/** @type {TableWatcherDef} */
export const coverageWatcherDef = {
id : 'CoverageWatcher',
testTable : (table) => hasCoverageData(table),
sharedData: { preparedTables: {}, tblCatIdMap: {}},
watcher : watchCoverage,
allowMultiples: false,
actions: [TABLE_LOADED, TABLE_SELECT,TABLE_HIGHLIGHT, TABLE_REMOVE, TBL_RESULTS_ACTIVE,
ImagePlotCntlr.PLOT_IMAGE, ImagePlotCntlr.PLOT_HIPS,
MultiViewCntlr.ADD_VIEWER, MultiViewCntlr.VIEWER_MOUNTED, MultiViewCntlr.VIEWER_UNMOUNTED]
};
const getOptions= (inputOptions) => ({...defOptions, ...cleanUpOptions(inputOptions)});
const centerId = (tbl_id) => (tbl_id+'_center');
const regionId = (tbl_id) => (tbl_id+'_region');
/**
* Action watcher callback: watch the tables and update coverage display
* @callback actionWatcherCallback
* @param tbl_id
* @param action
* @param cancelSelf
* @param params
* @param params.options read-only
* @param params.sharedData read-only
* @param params.sharedData.preparedTables
* @param params.sharedData.tblCatIdMap
* @param params.sharedData.preferredHipsSourceURL
* @param params.paused
*/
function watchCoverage(tbl_id, action, cancelSelf, params) {
const {sharedData} = params;
const options= getOptions(params.options);
const {viewerId}= options;
let paused = isUndefined(params.paused) ? options.paused : params.paused;
const {preparedTables, tblCatIdMap}= sharedData;
if (paused) {
paused= !get(getViewer(getMultiViewRoot(), viewerId),'mounted', false);
}
if (!action) {
if (!paused) {
preparedTables[tbl_id]= undefined;
sharedData.preferredHipsSourceURL= findPreferredHiPS(tbl_id, sharedData.preferredHipsSourceURL, options.hipsSourceURL, preparedTables[tbl_id]);
updateCoverage(tbl_id, viewerId, sharedData.preparedTables, options, tblCatIdMap, sharedData.preferredHipsSourceURL);
}
return;
}
const {payload}= action;
if (payload.tbl_id && payload.tbl_id!==tbl_id) return;
if (payload.viewerId && payload.viewerId!==viewerId) return;
if (action.type===REINIT_APP) {
sharedData.preparedTables= {};
cancelSelf();
return;
}
if (action.type===MultiViewCntlr.VIEWER_MOUNTED || action.type===MultiViewCntlr.ADD_VIEWER) {
sharedData.preferredHipsSourceURL= findPreferredHiPS(tbl_id, sharedData.preferredHipsSourceURL, options.hipsSourceURL, preparedTables[tbl_id]);
updateCoverage(tbl_id, viewerId, preparedTables, options, tblCatIdMap, sharedData.preferredHipsSourceURL);
return {paused:false};
}
if (action.type===TABLE_LOADED) preparedTables[tbl_id]= undefined;
if (paused) return {paused};
switch (action.type) {
case TABLE_LOADED:
if (!getTableInGroup(tbl_id)) return {paused};
sharedData.preferredHipsSourceURL= findPreferredHiPS(tbl_id, sharedData.preferredHipsSourceURL, options.hipsSourceURL, preparedTables[tbl_id]);
updateCoverage(tbl_id, viewerId, preparedTables, options, tblCatIdMap, sharedData.preferredHipsSourceURL);
break;
case TBL_RESULTS_ACTIVE:
if (!getTableInGroup(tbl_id)) return {paused};
sharedData.preferredHipsSourceURL= findPreferredHiPS(tbl_id, sharedData.preferredHipsSourceURL, options.hipsSourceURL, preparedTables[tbl_id]);
updateCoverage(tbl_id, viewerId, preparedTables, options, tblCatIdMap, sharedData.preferredHipsSourceURL);
break;
case TABLE_REMOVE:
removeCoverage(payload.tbl_id, preparedTables);
if (!isEmpty(preparedTables)) {
sharedData.preferredHipsSourceURL= findPreferredHiPS(tbl_id, sharedData.preferredHipsSourceURL, options.hipsSourceURL, preparedTables[tbl_id]);
updateCoverage(getActiveTableId(), viewerId, preparedTables, options, tblCatIdMap, sharedData.preferredHipsSourceURL);
}
cancelSelf();
break;
case TABLE_SELECT:
tblCatIdMap[tbl_id].forEach(( cId ) => {
dispatchModifyCustomField(cId, {selectInfo: action.payload.selectInfo});
});
break;
case TABLE_HIGHLIGHT:
case TABLE_UPDATE:
tblCatIdMap[tbl_id].forEach(( cId ) => {
dispatchModifyCustomField(cId, {highlightedRow: action.payload.highlightedRow});
});
break;
case MultiViewCntlr.VIEWER_UNMOUNTED:
paused = true;
break;
case ImagePlotCntlr.PLOT_IMAGE:
case ImagePlotCntlr.PLOT_HIPS:
if (action.payload.plotId===PLOT_ID) overlayCoverageDrawing(preparedTables,options, tblCatIdMap);
break;
}
return {paused};
}
function removeCoverage(tbl_id, preparedTables) {
if (tbl_id) Reflect.deleteProperty(preparedTables, tbl_id);
if (isEmpty(Object.keys(preparedTables))) {
dispatchDeletePlotView({plotId:PLOT_ID});
}
}
/**
* @param {string} tbl_id
* @param {string} viewerId
* @param preparedTables
* @param {CoverageOptions} options
* @param {object} tblCatIdMap
* @param {string} preferredHipsSourceURL
*/
function updateCoverage(tbl_id, viewerId, preparedTables, options, tblCatIdMap, preferredHipsSourceURL) {
try {
const table = getTblById(tbl_id);
if (!table) return;
if (preparedTables[tbl_id] === 'WORKING') return;
const params = {
startIdx: 0,
pageSize: MAX_ROW,
inclCols: getCovColumnsForQuery(options, table)
};
let req = cloneRequest(table.request, params);
if (table.totalRows > 10000) {
const cenCol = findTableCenterColumns(table);
if (!cenCol) return;
const sreq = cloneRequest(table.request, {inclCols: `"${cenCol.lonCol}","${cenCol.latCol}"`});
req = makeTableFunctionRequest(sreq, 'DecimateTable', 'coverage',
{decimate: serializeDecimateInfo(cenCol.lonCol, cenCol.latCol, 10000), pageSize: MAX_ROW});
}
req.tbl_id = `cov-${tbl_id}`;
if (preparedTables[tbl_id] /*&& preparedTables[tbl_id].tableMeta.resultSetID===table.tableMeta.resultSetID*/) { //todo support decimated data
updateCoverageWithData(viewerId, table, options, tbl_id, preparedTables[tbl_id], preparedTables,
isTableUsingRadians(table), tblCatIdMap, preferredHipsSourceURL );
}
else {
preparedTables[tbl_id] = 'WORKING';
doFetchTable(req).then(
(allRowsTable) => {
if (get(allRowsTable, ['tableData', 'data'], []).length > 0) {
preparedTables[tbl_id] = allRowsTable;
const isRegion = isTableWithRegion(allRowsTable);
//const isCatalog = findTableCenterColumns(allRowsTable);
tblCatIdMap[tbl_id] = (isRegion) ? [centerId(tbl_id), regionId(tbl_id)] : [tbl_id];
updateCoverageWithData(viewerId, table, options, tbl_id, allRowsTable, preparedTables,
isTableUsingRadians(table), tblCatIdMap, preferredHipsSourceURL );
}
}
).catch(
(reason) => {
preparedTables[tbl_id] = undefined;
tblCatIdMap[tbl_id] = undefined;
logError(`Failed to catalog plot data: ${reason}`, reason);
}
);
}
} catch (e) {
logError('Error updating coverage');
console.log(e);
}
}
/**
*
* @param {string} viewerId
* @param {TableData} table
* @param {CoverageOptions} options
* @param {string} tbl_id
* @param allRowsTable
* @param preparedTables
* @param usesRadians
* @param {object} tblCatIdMap
* @param {string} preferredHipsSourceURL
*/
function updateCoverageWithData(viewerId, table, options, tbl_id, allRowsTable, preparedTables,
usesRadians, tblCatIdMap, preferredHipsSourceURL ) {
const {maxRadius, avgOfCenters}= computeSize(options, preparedTables, usesRadians);
if (!avgOfCenters || maxRadius<=0) return;
const plot= primePlot(visRoot(), PLOT_ID);
if (plot &&
pointEquals(avgOfCenters,plot.attributes[COVERAGE_TARGET]) && plot.attributes[COVERAGE_RADIUS]===maxRadius ) {
overlayCoverageDrawing(preparedTables, options, tblCatIdMap);
return;
}
const {fovDegFallOver, fovMaxFitsSize, autoConvertOnZoom,
imageSourceParams, fovDegMinSize, overlayPosition= avgOfCenters}= options;
let plotAllSkyFirst= false;
let allSkyRequest= null;
const size= Math.max(maxRadius*2.2, fovDegMinSize);
if (size>160) {
allSkyRequest= WebPlotRequest.makeAllSkyPlotRequest();
allSkyRequest.setTitleOptions(TitleOptions.PLOT_DESC);
allSkyRequest= initRequest(allSkyRequest, viewerId, PLOT_ID, overlayPosition);
plotAllSkyFirst= true;
}
let imageRequest= WebPlotRequest.makeFromObj(imageSourceParams) ||
WebPlotRequest.make2MASSRequest(avgOfCenters, 'asky', 'k', size);
imageRequest= initRequest(imageRequest, viewerId, PLOT_ID, overlayPosition, avgOfCenters);
const hipsRequest= initRequest(WebPlotRequest.makeHiPSRequest(preferredHipsSourceURL, null),
viewerId, PLOT_ID, overlayPosition, avgOfCenters);
hipsRequest.setSizeInDeg(size);
dispatchPlotImageOrHiPS({
plotId: PLOT_ID, viewerId, hipsRequest, imageRequest, allSkyRequest,
fovDegFallOver, fovMaxFitsSize, autoConvertOnZoom, plotAllSkyFirst,
pvOptions: {userCanDeletePlots:false, displayFixedTarget:false},
attributes: {
[COVERAGE_TARGET]: avgOfCenters,
[COVERAGE_RADIUS]: maxRadius,
[COVERAGE_TABLE]: tbl_id,
[COVERAGE_CREATED]: true
}
});
}
/**
*
* @param r
* @param viewerId
* @param plotId
* @param overlayPos
* @param wp
* @return {*}
*/
function initRequest(r,viewerId,plotId, overlayPos, wp) {
if (!r) return undefined;
r= r.makeCopy();
r.setPlotGroupId(viewerId);
r.setPlotId(plotId);
r.setOverlayPosition(overlayPos);
if (wp) r.setWorldPt(wp);
return r;
}
/**
*
* @param {CoverageOptions} options
* @param preparedTables
* @param usesRadians
* @return {*}
*/
function computeSize(options, preparedTables, usesRadians) {
const ary= values(preparedTables);
const testAry= ary
.filter( (t) => t && t!=='WORKING')
.map( (t) => {
let ptAry= [];
const covType= getCoverageType(options,t);
switch (covType) {
case CoverageType.X:
ptAry= getPtAryFromTable(options,t, usesRadians);
break;
case CoverageType.BOX:
ptAry= getBoxAryFromTable(options,t, usesRadians);
break;
case CoverageType.REGION:
ptAry = getRegionAryFromTable(options, t, usesRadians);
break;
}
return flattenDeep(ptAry);
} );
return computeCentralPtRadiusAverage(testAry);
}
function makeOverlayCoverageDrawing() {
const drawingOptions= {};
const selectOps = {};
/**
*
* @param preparedTables
* @param {CoverageOptions} options
* @param {object} tblCatIdMap
*/
return (preparedTables, options, tblCatIdMap) => {
const plot= primePlot(visRoot(),PLOT_ID);
if (!plot) return;
const tbl_id= plot.attributes[COVERAGE_TABLE];
if (!tbl_id || !preparedTables[tbl_id] || !getTblById(tbl_id)) return;
const table= getTblById(tbl_id);
if (isCatalog(table) && options.ignoreCatalogs) return; // let the catalog watcher just handle the drawing overlays
if (tblCatIdMap[tbl_id]) {
tblCatIdMap[tbl_id].forEach((cId) => {
const layer = getDrawLayerById(getDlAry(), cId);
if (layer) {
drawingOptions[cId] = layer.drawingDef; // drawingDef and selectOption is stored as layer based
selectOps[cId] = layer.selectOption;
dispatchDestroyDrawLayer(cId);
}
});
}
const overlayAry= Object.keys(preparedTables);
overlayAry.forEach( (id) => {
tblCatIdMap[id] && tblCatIdMap[id].forEach((cId) => {
if (!drawingOptions[cId]) drawingOptions[cId] = {};
if (!drawingOptions[cId].color) drawingOptions[cId].color = lookupOption(options, 'color', cId) || getNextColor();
if (selectOps[cId]) drawingOptions[cId].selectOption = selectOps[cId];
});
const oriTable= getTblById(id);
const arTable= preparedTables[id];
if (oriTable && arTable) addToCoverageDrawing(PLOT_ID, options, oriTable, arTable, drawingOptions);
});
};
}
/**
*
* @param {string} plotId
* @param {CoverageOptions} options
* @param {TableData} table
* @param {TableData} allRowsTable
* @param {string} drawOp
*/
function addToCoverageDrawing(plotId, options, table, allRowsTable, drawOp) {
if (allRowsTable==='WORKING') return;
const covType= getCoverageType(options,allRowsTable);
const {tableMeta, tableData}= allRowsTable;
const angleInRadian= isTableUsingRadians(tableMeta);
const createDrawLayer = (cId, dataType, isFromRegion=false) => {
const columns = dataType === CoverageType.REGION ? findTableRegionColumn(allRowsTable) :
(covType === CoverageType.BOX ? getCornersColumns(allRowsTable) : findTableCenterColumns(allRowsTable));
if (isEmpty(columns)) return;
const dl = getDlAry().find((dl) => dl.drawLayerTypeId === Catalog.TYPE_ID && dl.catalogId === cId);
if (!dl) {
const {showCatalogSearchTarget}= getAppOptions();
const searchTarget= showCatalogSearchTarget ? getSearchTarget(table.request,
lookupOption(options, 'searchTarget', cId),
lookupOption(options, 'overlayPosition', cId))
: undefined;
dispatchCreateDrawLayer(Catalog.TYPE_ID, {
catalogId: cId,
tblId: table.tbl_id,
title: `Coverage: ${table.title || table.tbl_id}` +
(isFromRegion ? (dataType===CoverageType.REGION ? ' regions' : ' positions') : ''),
color: drawOp[cId].color,
tableData,
tableMeta,
tableRequest: table.request,
highlightedRow: table.highlightedRow,
catalog: dataType === CoverageType.X,
boxData: dataType !== CoverageType.X,
dataType: dataType.key,
isFromRegion,
columns,
symbol: drawOp.symbol || lookupOption(options, 'symbol', cId),
size: drawOp.size || lookupOption(options, 'symbolSize', cId),
selectInfo: table.selectInfo,
angleInRadian,
dataTooBigForSelection: table.totalRows > 10000,
tableSelection: (dataType === CoverageType.REGION) ? (drawOp[cId].selectOption || TableSelectOptions.all.key) : null,
searchTarget,
});
dispatchAttachLayerToPlot(cId, plotId);
}
};
if (covType === CoverageType.BOX) {
createDrawLayer(table.tbl_id, covType);
} else if (covType === CoverageType.X) {
createDrawLayer(table.tbl_id, covType);
} else if (covType === CoverageType.REGION) {
const layerType = [CoverageType.X, CoverageType.REGION];
const layerId = [centerId(table.tbl_id), regionId(table.tbl_id)];
layerType.forEach((type, idx) => createDrawLayer(layerId[idx], layerType[idx], true));
}
}
/**
* look up a value from the CoverageOptions
* @param {CoverageOptions} options
* @param {string} key
* @param {string} tbl_id
* @return {*}
*/
function lookupOption(options, key, tbl_id) {
const value= options[key];
if (!value) return undefined;
return isObject(value) ? value[tbl_id] : value;
}
function getCoverageType(options,table) {
if (options.coverageType===CoverageType.GUESS ||
options.coverageType===CoverageType.REGION ||
options.coverageType===CoverageType.BOX ||
options.coverageType===CoverageType.ALL) {
return isTableWithRegion(table) ? CoverageType.REGION :
(hasCorners(options,table) ? CoverageType.BOX : CoverageType.X);
}
return options.coverageType;
}
function hasCorners(options, table) {
const cornerColumns= getCornersColumns(table);
if (isEmpty(cornerColumns)) return false;
const dataCnt= table.tableData.data.reduce( (tot, row) =>
cornerColumns.every( (cDef) => row[cDef.lonIdx]!=='' && row[cDef.latIdx]!=='') ? tot+1 : tot
,0);
return dataCnt/table.tableData.data.length > .1;
}
function toAngle(d, radianToDegree) {
const v= Number(d);
return (!isNaN(v) && radianToDegree) ? toDegrees(v): v;
}
function makePt(lonStr,latStr, csys, radianToDegree) {
return makeWorldPt(toAngle(lonStr,radianToDegree), toAngle(latStr, radianToDegree), csys);
}
function getPtAryFromTable(options,table, usesRadians){
const cDef= findTableCenterColumns(table);
if (isEmpty(cDef)) return [];
const {lonIdx,latIdx,csys}= cDef;
return table.tableData.data
.map( (row) =>
(row[lonIdx]!=='' && row[latIdx]!=='') ? makePt(row[lonIdx], row[latIdx], csys, usesRadians) : undefined )
.filter( (v) => v);
}
function getBoxAryFromTable(options,table, usesRadians){
const cDefAry= getCornersColumns(table);
return table.tableData.data
.map( (row) => cDefAry
.map( (cDef) =>
(row[cDef.lonIdx]!=='' && row[cDef.latIdx]!=='') ? makePt(row[cDef.lonIdx], row[cDef.latIdx], cDef.csys, usesRadians) : undefined))
.filter( (row) => row.every( (v) => v));
}
function getRegionAryFromTable(options, table, usesRadians) {
const rCol = findTableRegionColumn(table);
return table.tableData.data.map((row) => {
const cornerInfo = parseObsCoreRegion(row[rCol.regionIdx], rCol.unit, true);
return cornerInfo.valid ? cornerInfo.corners : [];
}).filter((r) => !isEmpty(r));
}
function getCovColumnsForQuery(options, table) {
const cAry= [...getCornersColumns(table), findTableCenterColumns(table), findTableRegionColumn(table)];
// column names should be in quotes
// there should be no duplicates
const base = cAry.filter((c)=>!isEmpty(c))
.map( (c)=> (c.type === 'region') ? `"${c.regionCol}"` : `"${c.lonCol}","${c.latCol}"`)
.filter((v,i,a) => a.indexOf(v) === i)
.join();
return base+',"ROW_IDX"';
}
function cleanUpOptions(options) {
const opStrList= Object.keys(defOptions);
return Object.keys(options).reduce( (result, key) => {
const properKey= opStrList.find( (testKey) => testKey.toLowerCase()===key.toLowerCase());
result[properKey||key]= options[key];
return result;
},{});
}
/**
*
* @param tbl_id
* @param prevPreferredHipsSourceURL
* @param optionHipsSourceURL
* @param preparedTable
* @return {*}
*/
function findPreferredHiPS(tbl_id,prevPreferredHipsSourceURL, optionHipsSourceURL, preparedTable) {
if (!preparedTable) { // if a new table then the meta takes precedence
const table = getTblById(tbl_id);
if (table && table.tableMeta[MetaConst.COVERAGE_HIPS]) return table.tableMeta[MetaConst.COVERAGE_HIPS];
}
const plot= primePlot(visRoot(), PLOT_ID);
if (isHiPS(plot)) {
return plot.hipsUrlRoot;
}
if (prevPreferredHipsSourceURL) return prevPreferredHipsSourceURL;
if (optionHipsSourceURL) return optionHipsSourceURL;
}