diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/LockingVisNetwork.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/LockingVisNetwork.java index e0f7dfc4c..18fc9de59 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/LockingVisNetwork.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/LockingVisNetwork.java @@ -20,8 +20,10 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -34,6 +36,7 @@ public class LockingVisNetwork { private static final Map _activeRequest = Collections.synchronizedMap(new HashMap<>()); + private static final Map activeListeners = Collections.synchronizedMap(new HashMap<>()); public static FileInfo retrieveURL(AnyUrlParams params) throws FailedRequestException { return lockingRetrieve(params, null); @@ -64,9 +67,10 @@ private static FileInfo lockingRetrieve(BaseNetParams params, ServiceCaller svcC confirmParamsType(params); try { Object lockKey= _activeRequest.computeIfAbsent(params, k -> new Object()); + DownloadProgress dl= makeDownloadProgressOrFind(params); synchronized (lockKey) { return (params instanceof AnyUrlParams urlP) ? - retrieveURL( urlP, makeDownloadProgress(params) ) : + retrieveURL(urlP, dl) : (params instanceof ImageServiceParams isParam) ? retrieveService(isParam, svcCaller) : retrieveServiceCaller((ServiceCallerParams) params); @@ -78,9 +82,22 @@ private static FileInfo lockingRetrieve(BaseNetParams params, ServiceCaller svcC } } - private static DownloadProgress makeDownloadProgress(BaseNetParams params) { // todo: generalize beyond just plotId + + + private static DownloadProgress makeDownloadProgressOrFind(BaseNetParams params) { // todo: generalize beyond just plotId if (params.getStatusKey()== null) return null; - return new DownloadProgress(params.getStatusKey(), params.getPlotId()); + if (!(params instanceof AnyUrlParams)) return null; + DownloadProgress dl; + + if (activeListeners.containsKey(params)) { + dl = activeListeners.get(params); + dl.addPlotId(params.getPlotId()); + } + else { + dl= new DownloadProgress(params.getStatusKey(), params.getPlotId()); + activeListeners.put(params, dl); + } + return dl; } private static void confirmParamsType(NetParams params) throws FailedRequestException { @@ -122,6 +139,7 @@ private static FileInfo retrieveURL(AnyUrlParams params, DownloadListener dl) th var ops= URLDownload.Options.listenerOp(params.getMaxSizeToDownload(), dl); fileInfo= URLDownload.getDataToFile(params.getURL(), fileName, params.getCookies(), params.getHeaders(), ops); if (fileInfo.getResponseCode()==200) CacheHelper.putFileInfo(params,fileInfo); + activeListeners.remove(params); return fileInfo; } catch (Exception e) { Logger.warn(e.toString()); @@ -139,13 +157,15 @@ public interface CanCallService {}; private static class DownloadProgress implements DownloadListener { private final String _key; - private final String _plotId; + private final List plotIdList= new ArrayList<>(); DownloadProgress(String key, String plotId) { _key = key; - _plotId = plotId; + plotIdList.add(plotId); } + void addPlotId(String plotId) {plotIdList.add(plotId);} + public void dataDownloading(DownloadEvent ev) { if (_key == null) return; String offStr = ""; @@ -155,7 +175,9 @@ public void dataDownloading(DownloadEvent ev) { offStr = " of " + FileUtil.getSizeAsString(ev.getMax(),true); } String messStr = "Retrieved " + FileUtil.getSizeAsString(current,true) + offStr; - PlotServUtils.updateProgress(_key, _plotId, ProgressStat.PType.DOWNLOADING, messStr); + for(var plotId : plotIdList) { + PlotServUtils.updateProgress(_key,plotId, ProgressStat.PType.DOWNLOADING, messStr); + } } } diff --git a/src/firefly/java/edu/caltech/ipac/table/io/SpectrumMetaInspector.java b/src/firefly/java/edu/caltech/ipac/table/io/SpectrumMetaInspector.java index 35c947c25..6566b0422 100644 --- a/src/firefly/java/edu/caltech/ipac/table/io/SpectrumMetaInspector.java +++ b/src/firefly/java/edu/caltech/ipac/table/io/SpectrumMetaInspector.java @@ -76,7 +76,7 @@ public class SpectrumMetaInspector { private static final String SPEC_TI_AXIS_ACCURACY= SPEC_TI_AXIS+ACCURACY; private static final String SPEC_ORDER = "spec:Data.SpectralAxis.Order"; private static final String SPEC_REL_ORDER= "spec:Data.SpectralAxis.RelOrder"; - private static final String spec10Version= "Spectrum v1.0"; + private static final String spec10Version= "spectrum v1.0"; private static final String VOCLASS= "VOCLASS"; private static final String SPEC_SPECTRUM= "spec:Spectrum"; @@ -116,12 +116,9 @@ public static void searchForSpectrum(DataGroup dg, BasicHDU hdu, boolean spec String utype; if (hdu!=null) { Header h= hdu.getHeader(); - String voclass= h.getStringValue(VOCLASS); - if (voclass==null) voclass= ""; + String voclass= h.getStringValue(VOCLASS,"").toLowerCase(); utype= FitsReadUtil.getUtype(h); - if (utype==null && !spectrumHint && - !voclass.equals(spec10Version) && - !voclass.toLowerCase().startsWith("spectrum") ) { + if (utype==null && !spectrumHint && (voclass.equals(spec10Version) || voclass.startsWith("spectrum") )) { utype= SPEC_SPECTRUM; } if (utype!=null) { diff --git a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java index 11c24d434..216d95db1 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java @@ -221,7 +221,7 @@ public static HttpResultInfo getDataFromURL(URL url, Map> reqProp= conn.getRequestProperties(); pushPostData(conn, postData); - logHeader(postData, conn, reqProp); + logHeader(url.toString(), postData, conn, reqProp); ByteArrayOutputStream out = new ByteArrayOutputStream(4096); netCopy(makeAnyInStream(conn, false), out, conn, 0, null); byte[] results = out.toByteArray(); @@ -247,7 +247,7 @@ public static HttpResultInfo getHeaderFromURL(URL url, conn.setReadTimeout(timeoutInSec * 1000); } ((HttpURLConnection)conn).setRequestMethod("HEAD"); - logHeader(null, conn, conn.getRequestProperties()); + logHeader(url.toString(), null, conn, conn.getRequestProperties()); Set>> hSet = getResponseCode(conn)==-1 ? null : conn.getHeaderFields().entrySet(); HttpResultInfo result= new HttpResultInfo(null,getResponseCode(conn),conn.getContentType(),getSugestedFileName(conn)); @@ -367,6 +367,7 @@ public static FileInfo getDataToFile(URLConnection conn, int redirectCnt) throws FailedRequestException { try { + String originalUrl= conn.getURL().toString(); FileInfo outFileData; Map> reqProp = conn.getRequestProperties(); Map> sendHeaders = null; @@ -396,7 +397,7 @@ public static FileInfo getDataToFile(URLConnection conn, //------ //---From here on the server should be responding //------ - logHeader(postData, conn, sendHeaders); + logHeader(originalUrl, postData, conn, sendHeaders); validFileSize(conn, ops.maxFileSize); netCopy(makeAnyInStream(conn, ops.uncompress), makeOutStream(outfile), conn, ops.maxFileSize, ops.dl); long elapse = System.currentTimeMillis() - start; @@ -562,9 +563,11 @@ private static void logError(URL url, Map postData, Exception e) _log.warn(strList.toArray(new String[0])); } - public static void logHeader(URLConnection conn) { logHeader(null, conn, null); } + public static void logHeader(URLConnection conn) { logHeader(null,null, conn, null); } - private static void logHeader(Map postData, URLConnection conn, Map> sendHeaders) { + public static void logHeader(String originalUrl, URLConnection conn) { logHeader(originalUrl,null, conn, null); } + + private static void logHeader(String originalUrl, Map postData, URLConnection conn, Map> sendHeaders) { StringBuffer workBuff; try { String verb= ""; @@ -575,6 +578,9 @@ private static void logHeader(Map postData, URLConnection conn, M if (conn.getURL() != null) { outStr.add("----------Sending " + verb); outStr.add( conn.getURL().toString()); + if (originalUrl!=null && !conn.getURL().toString().equals(originalUrl)) { + outStr.add( StringUtils.pad(20, "Original URL")+": "+originalUrl); + } if (sendHeaders!=null) { for(Map.Entry> se: sendHeaders.entrySet()) { workBuff = new StringBuffer(100); diff --git a/src/firefly/js/core/ClickToAction.js b/src/firefly/js/core/ClickToAction.js index 5259275ea..ed2171b98 100644 --- a/src/firefly/js/core/ClickToAction.js +++ b/src/firefly/js/core/ClickToAction.js @@ -55,13 +55,13 @@ export function makeSearchActionObj({ cmd, groupId, label='', tip, searchType, m return {cmd, label, tip, searchType, min, max, supported, execute, verb, searchDesc, groupId}; } -export function getSearchTypeDesc(sa, wp, size, areaPtsLength) { +export function getSearchTypeDesc({sa, wp, size, areaPtsLength,tbl_id}) { const {searchDesc, searchType, verb, label, min, max}= sa; if (searchDesc) { if (isString(searchDesc)) return searchDesc; if (isFunction(searchDesc)) { - const title= searchDesc(wp,size,areaPtsLength); + const title= searchDesc({wp,size,areaPtsLength,tbl_id}); if (isString(title)) return title; } } diff --git a/src/firefly/js/data/MetaConst.js b/src/firefly/js/data/MetaConst.js index 96ee282e3..fe96da9fa 100644 --- a/src/firefly/js/data/MetaConst.js +++ b/src/firefly/js/data/MetaConst.js @@ -34,6 +34,11 @@ export const MetaConst = { */ FITS_EXTRACTION_TYPE: 'FitsExtractionType', + /** + * Search Target - the search target associated with this table + */ + SEARCH_TARGET: 'SearchTarget', + /** * An world point in a fits FILE that is associated with this table */ @@ -182,6 +187,11 @@ export const MetaConst = { /** the column name with public release date info; null is considered not public */ RELEASE_DATE_COL : 'RELEASE_DATE_COL', + /** + * any ID can be added to a table search to identify the source for the purpose of getting source specific obscore preferences + */ + OBSCORE_SOURCE_ID: 'OBSCORE_SOURCE_ID', + /** * if defined this table container a moving object, setting this will override any catalog evaluation * value can me true or false, if true then evaluate this table as a moving obj, if false then ignore @@ -229,6 +239,12 @@ export const MetaConst = { */ UPLOAD_TABLE: 'UploadTable', + /** + * if defined and an object, Then this will be table specific DataProductsFactory options + * @see DataProductsFactoryOptions + */ + DATA_PRODUCTS_FACTORY_OPTIONS: 'DataProductFactoryOptions', + /** @deprecated use CENTER_COLUMN */ CATALOG_COORD_COLS : 'CatalogCoordColumns', diff --git a/src/firefly/js/drawingLayers/HiPSMOC.js b/src/firefly/js/drawingLayers/HiPSMOC.js index a6149a163..044e394d7 100644 --- a/src/firefly/js/drawingLayers/HiPSMOC.js +++ b/src/firefly/js/drawingLayers/HiPSMOC.js @@ -61,15 +61,20 @@ function loadMocFitsWatcher(action, cancelSelf, params, dispatch, getState) { if (!dl) return; const preloadedTbl= tablePreloaded && getTblById(tbl_id); + const filterObj= dl.maxFetchDepth ? {filters : `"${mocFitsInfo.uniqColName}" < ${4*(4**(dl.maxFetchDepth+1))}`} : {}; if (!dl.mocTable) { // moc table is not yet loaded let tReq; if (preloadedTbl){ //load by getting the full version of a already loaded table tReq= cloneRequest(preloadedTbl.request, - { startIdx : 0, pageSize : MAX_ROW, inclCols: mocFitsInfo.uniqColName }); + { startIdx : 0, pageSize : MAX_ROW, inclCols: mocFitsInfo.uniqColName, ...filterObj}); } else if (fitsPath) { // load by getting file on server + tReq = makeTblRequest('userCatalogFromFile', 'Table Upload', - {filePath: fitsPath, sourceFrom: 'isLocal'}, + { + filePath: fitsPath, sourceFrom: 'isLocal', + ...filterObj, + }, {tbl_id: mocFitsInfo.tbl_id, pageSize: MAX_ROW, inclCols: mocFitsInfo.uniqColName}); } if (!tReq) return; @@ -162,6 +167,7 @@ function creator(initPayload) { dl.mocTable= undefined; dl.rootTitle= dl.title; dl.mocGroupDefColorId= mocGroupDefColorId; + dl.maxFetchDepth= initPayload.maxFetchDepth; dispatchAddActionWatcher({ callback:loadMocFitsWatcher, diff --git a/src/firefly/js/drawingLayers/PointSelection.js b/src/firefly/js/drawingLayers/PointSelection.js index 39fafa803..8f9f68eb3 100644 --- a/src/firefly/js/drawingLayers/PointSelection.js +++ b/src/firefly/js/drawingLayers/PointSelection.js @@ -45,7 +45,7 @@ function dispatchSelectPoint(mouseStatePayload) { function onDetach(drawLayer,action) { const {plotIdAry}= action.payload; - plotIdAry.forEach( (plotId) => { + plotIdAry?.forEach( (plotId) => { const plot= primePlot(visRoot(),plotId); if (plot && plot.attributes[PlotAttribute.ACTIVE_POINT]) { dispatchAttributeChange( diff --git a/src/firefly/js/metaConvert/AnalysisUtils.js b/src/firefly/js/metaConvert/AnalysisUtils.js index f1b755de2..b9dee37ee 100644 --- a/src/firefly/js/metaConvert/AnalysisUtils.js +++ b/src/firefly/js/metaConvert/AnalysisUtils.js @@ -115,18 +115,19 @@ export function makeAnalysisGetGridDataProduct(makeReq) { * @param obj.menuKey * @param obj.dataTypeHint * @param {ServiceDescriptorDef} [obj.serDef] + * @param {DatalinkData} [obj.dlData] * @param [obj.originalTitle] * @param {DataProductsFactoryOptions} [obj.options] * @return {function} */ export function makeAnalysisActivateFunc({table, row, request, activateParams, menuKey, - dataTypeHint, serDef, originalTitle, options}) { + dataTypeHint, serDef, originalTitle, options, dlData}) { const analysisActivateFunc = async (menu, userInputParams) => { const {dpId}= activateParams; dispatchUpdateDataProducts(dpId, dpdtWorkingMessage(LOADING_MSG,menuKey)); // do the uploading and analyzing const dPDisplayType= await doUploadAndAnalysis({ table, row, request, activateParams, dataTypeHint, options, menu, - serDef, userInputParams, analysisActivateFunc, originalTitle, menuKey}); + serDef, dlData, userInputParams, analysisActivateFunc, originalTitle, menuKey}); // activate the result of the analysis dispatchResult(dPDisplayType, menu,menuKey,dpId, serDef, analysisActivateFunc); }; diff --git a/src/firefly/js/metaConvert/DataProductsCntlr.js b/src/firefly/js/metaConvert/DataProductsCntlr.js index a6089d65e..98cd1d450 100644 --- a/src/firefly/js/metaConvert/DataProductsCntlr.js +++ b/src/firefly/js/metaConvert/DataProductsCntlr.js @@ -47,6 +47,7 @@ function activateMenuItemActionCreator(rawAction) { const menuItem= menu.find( (m) => m.menuKey===menuKey); if (!menuItem) return; if (menuItem.displayType===DPtypes.DOWNLOAD) doDownload(menuItem.url); + else if (menuItem.displayType===DPtypes.EXTRACT) doExtract(menuItem); else dispatcher(rawAction); }; } @@ -66,6 +67,10 @@ function activateFileMenuItemActionCreator(rawAction) { }; } +function doExtract(menuItem) { + menuItem?.activate(); +} + export function doDownload(url) { const serviceURL = getRootURL() + 'servlet/Download'; const tmpUrl= url.toLowerCase(); diff --git a/src/firefly/js/metaConvert/DataProductsFactory.js b/src/firefly/js/metaConvert/DataProductsFactory.js index 26aa89961..c6017a84e 100644 --- a/src/firefly/js/metaConvert/DataProductsFactory.js +++ b/src/firefly/js/metaConvert/DataProductsFactory.js @@ -4,7 +4,7 @@ import {isArray, once} from 'lodash'; import {MetaConst} from '../data/MetaConst.js'; -import {getMetaEntry} from '../tables/TableUtil'; +import {getMetaEntry, getObjectMetaEntry} from '../tables/TableUtil'; import {hasObsCoreLikeDataProducts, isDatalinkTable} from '../voAnalyzer/TableAnalysis.js'; import {hasServiceDescriptors} from '../voAnalyzer/VoDataLinkServDef.js'; import {Band} from '../visualize/Band'; @@ -284,14 +284,13 @@ function initConverterTemplates() { * @typedef {Object} DataProductsFactoryOptions * * @prop {boolean} [allowImageRelatedGrid] - * @prop {boolean} [allowServiceDefGrid] - // todo: this is redundant, should remove * @prop {boolean} [singleViewImageOnly] - if true, the view will only show the primary image product * @prop {boolean} [singleViewTableOnly] - if true, the view will only show the primary table product * @prop {String} [dataLinkInitialLayout] - determine how a datalink obscore table trys to show the data layout, must be 'single', 'gridRelated', 'gridFull'; * @prop {boolean} [activateServiceDef] - if possible then call the service def without giving option for user input * @prop {boolean} [threeColor] - if true, then for images in related grid, show the threeColor option * @prop {number} [maxPlots] - maximum number of plots in grid mode, this will override the factory default - * @prop {boolean} [canGrid] - some specific factories might have parameters that override this parameter (e.g. allowServiceDefGrid) + * @prop {boolean} [canGrid] - some specific factories might have parameters that override this parameter * @prop [initialLayout] * @prop {string} [tableIdBase] - any tbl_id will use this string for its base * @prop {string} [chartIdBase] - any chartId will use this string for its base @@ -302,6 +301,7 @@ function initConverterTemplates() { * The values in this object will override one or more parameters to a service descriptor. * The following are used with this prop by service descriptors to build the url to include input from the UI. * see- ServDescProducts.js getComponentInputs() + * @prop {object} datalinkTblRequestOptions = add addition options to table request * @prop {Array.} [paramNameKeys] - name of the parameters to put in the url from the getComponentState() return object * @prop {Array.} [ucdKeys] - same as above but can be specified by UCD * @prop {Array.} [utypeKeys] - same as above but can be specified by utype @@ -313,19 +313,19 @@ function initConverterTemplates() { export const getDefaultFactoryOptions= once(() => ({ dataProductsComponentKey: DEFAULT_DATA_PRODUCTS_COMPONENT_KEY, allowImageRelatedGrid: false, - allowServiceDefGrid: false, // todo: this is redundant, should remove singleViewImageOnly:false, singleViewTableOnly:false, dataLinkInitialLayout: 'single', //determine how a datalink obscore table trys to show the data layout, must be 'single', 'gridRelated', 'gridFull'; activateServiceDef: false, threeColor: undefined, maxPlots: undefined, - canGrid: undefined, // some specific factories might have parameters that override this parameter (e.g. allowServiceDefGrid) + canGrid: undefined, // some specific factories might have parameters that override this parameter initialLayout: undefined, //todo - an datalink use this? tableIdBase: undefined, chartIdBase: undefined, tableIdList: [], // list of ids chartIdList: [],// list of ids + datalinkTblRequestOptions: {}, paramNameKeys: [], ucdKeys: [], utypeKeys: [], @@ -386,9 +386,9 @@ export function makeDataProductsConverter(table, factoryKey= undefined) { threeColor: options.threeColor ?? t.threeColor ?? false, dataProductsComponentKey: options.dataProductsComponentKey }; - const retObj= t.create(table,pT, options); + const metaOptions= getObjectMetaEntry(table, MetaConst.DATA_PRODUCTS_FACTORY_OPTIONS, {}); + const retObj= t.create(table,pT, {...options, ...metaOptions}); return {options, ...retObj}; - } diff --git a/src/firefly/js/metaConvert/DataProductsType.js b/src/firefly/js/metaConvert/DataProductsType.js index 9ae3bb1c2..c822fb987 100644 --- a/src/firefly/js/metaConvert/DataProductsType.js +++ b/src/firefly/js/metaConvert/DataProductsType.js @@ -74,6 +74,7 @@ export const DPtypes= { CHOICE_CTI: 'chartTable', DOWNLOAD: 'download', DOWNLOAD_MENU_ITEM: 'download-menu-item', + EXTRACT: 'extract', PNG: 'png', ANALYZE: 'analyze', UNSUPPORTED: 'unsupported', @@ -184,9 +185,9 @@ export const dpdtMessageWithError= (message,detailMsgAry) => { */ export function dpdtImage({name, activate, extraction, menuKey='image-0', extractionText='Pin Image', request, override, interpretedData, requestDefault, enableCutout, pixelBasedCutout, - url, semantics,size, serDef }) { + url, semantics,size, serDef, dlData }) { return { displayType:DPtypes.IMAGE, name, activate, extraction, menuKey, extractionText, enableCutout, pixelBasedCutout, - request, override, interpretedData, requestDefault,url, semantics,size,serDef}; + request, override, interpretedData, requestDefault,url, semantics,size,serDef, dlData}; } /** @@ -234,6 +235,7 @@ export function dpdtChartTable(name, activate, extraction, menuKey='chart-table- * @param {boolean} [p.allowsInput] * @param {String} [p.standardID] * @param {String} [p.ID] + * @param {DatalinkData} [dlData] * @return {DataProductsDisplayType} */ export function dpdtAnalyze({ @@ -251,11 +253,13 @@ export function dpdtAnalyze({ serviceDefRef, allowsInput= false, standardID, - ID }) { + ID, + cutoutToFullWarning, + dlData}) { return { displayType:DPtypes.ANALYZE, name, url, activate, serDef, menuKey, semantics, size, activeMenuLookupKey, request, sRegion, prodTypeHint, - serviceDefRef, allowsInput, standardID, ID, + cutoutToFullWarning, serviceDefRef, allowsInput, standardID, ID, dlData }; } @@ -272,6 +276,10 @@ export function dpdtDownload(name, url, menuKey='download-0', fileType, extra={} return { displayType:DPtypes.DOWNLOAD, name, url, menuKey, fileType, ...extra}; } +export function dpdtExtract(name, activate, menuKey='extract-0') { + return { displayType:DPtypes.EXTRACT, name, activate, menuKey}; +} + export function dpdtDownloadMenuItem(name, url, menuKey='download-0', fileType, extra={}) { return { displayType:DPtypes.DOWNLOAD_MENU_ITEM, name, url, menuKey, singleDownload: true, fileType, ...extra}; } diff --git a/src/firefly/js/metaConvert/DataProductsWatcher.js b/src/firefly/js/metaConvert/DataProductsWatcher.js index 9ca15c06f..8edc507ea 100644 --- a/src/firefly/js/metaConvert/DataProductsWatcher.js +++ b/src/firefly/js/metaConvert/DataProductsWatcher.js @@ -255,9 +255,17 @@ function updateDataProducts(factoryKey, action, firstTime, tbl_id, activateParam const {highlightedRow}= tableState; // keep the plotId array for 'single' layout - const layout= getLayoutType(getMultiViewRoot(), imageViewerId, tbl_id); + let layout= getLayoutType(getMultiViewRoot(), imageViewerId, tbl_id); const layoutDetail= getLayoutDetails(getMultiViewRoot(), imageViewerId, tbl_id); + + if (layout===GRID && !converter.canGrid) { + dispatchChangeViewerLayout(viewer.viewerId,SINGLE,undefined,tbl_id); + layout= getLayoutType(getMultiViewRoot(), imageViewerId, tbl_id); + } + + + let resultPromise; if (layout===SINGLE) { resultPromise= converter.getSingleDataProduct(table,highlightedRow,activateParams, options); diff --git a/src/firefly/js/metaConvert/ImageDataProductsUtil.js b/src/firefly/js/metaConvert/ImageDataProductsUtil.js index c2feb0ace..6412427e0 100644 --- a/src/firefly/js/metaConvert/ImageDataProductsUtil.js +++ b/src/firefly/js/metaConvert/ImageDataProductsUtil.js @@ -18,9 +18,7 @@ import {getPlotGroupById} from '../visualize/PlotGroup.js'; import { getActivePlotView, getPlotViewAry, getPlotViewById, isDefaultCoverageActive, isImageExpanded, primePlot } from '../visualize/PlotViewUtil.js'; -import { - AnnotationOps, getDefaultImageColorTable, isImageDataRequestedEqual, WebPlotRequest -} from '../visualize/WebPlotRequest.js'; +import { getDefaultImageColorTable, isImageDataRequestedEqual, WebPlotRequest } from '../visualize/WebPlotRequest.js'; import {ZoomType} from '../visualize/ZoomType.js'; @@ -101,22 +99,27 @@ function copyRequest(inR) { /** * pass a request or array of request and return an extraction function * @param {WebPlotRequest|Array.} request + * @param {ObsCoreData} [sourceObsCoreData] + * @param {DatalinkData} [dlData] * @return {Function} */ -export function createSingleImageExtraction(request) { +export function createSingleImageExtraction(request, sourceObsCoreData, dlData) { if (!request) return undefined; const wpRequest= isArray(request) ? request.map( (r) => copyRequest(r)) : copyRequest(request); const plotIds= isArray(request) ? request.map( (r) => r.getPlotId()) : copyRequest(request); + const attributes= {}; + if (sourceObsCoreData) attributes.sourceObsCoreData= sourceObsCoreData; + if (dlData) attributes.dlData= dlData; return () => { if (isArray(wpRequest)) { const activePlotId= getActivePlotView(visRoot())?.plotId; const idx= plotIds.findIndex( (id) => id===activePlotId); if (idx<0) return; dispatchPlotImage({ viewerId:DEFAULT_FITS_VIEWER_ID, - plotId:wpRequest[idx].getPlotId(),wpRequest:wpRequest[idx]}); + plotId:wpRequest[idx].getPlotId(),wpRequest:wpRequest[idx], attributes}); } else { - dispatchPlotImage({ viewerId:DEFAULT_FITS_VIEWER_ID, wpRequest}); + dispatchPlotImage({ viewerId:DEFAULT_FITS_VIEWER_ID, wpRequest, attributes}); } showPinMessage('Pinning to Image Area'); }; diff --git a/src/firefly/js/metaConvert/PartAnalyzer.js b/src/firefly/js/metaConvert/PartAnalyzer.js index c5960a5f8..311189243 100644 --- a/src/firefly/js/metaConvert/PartAnalyzer.js +++ b/src/firefly/js/metaConvert/PartAnalyzer.js @@ -4,6 +4,7 @@ import {isArray,isEmpty} from 'lodash'; import {getProdTypeGuess, getSSATitle, isSSATable} from '../voAnalyzer/TableAnalysis.js'; import {isFitsTableDataTypeNumeric} from '../visualize/FitsHeaderUtil.js'; +import {getObsCoreData} from '../voAnalyzer/VoDataLinkServDef'; import {dpdtChartTable, dpdtImage, dpdtTable, DPtypes, SHOW_CHART, SHOW_TABLE, AUTO} from './DataProductsType'; import {FileAnalysisType, Format, UIEntry, UIRender} from '../data/FileAnalysis'; import {RequestType} from '../visualize/RequestType.js'; @@ -13,18 +14,21 @@ import {createSingleImageActivate, createSingleImageExtraction} from './ImageDat /** * - * @param {FileAnalysisPart} part - * @param {WebPlotRequest} request - * @param {TableModel} table - * @param {number} row - * @param {String} fileFormat (see Format object) - * @param {String} dataTypeHint stuff like 'spectrum', 'image', 'cube', etc - * @param serverCacheFileKey - * @param {ActivateParams} activateParams - * @param {DataProductsFactoryOptions} options + * @param {Object} p + * @param {FileAnalysisPart} p.part + * @param {WebPlotRequest} p.request + * @param {TableModel} p.table + * @param {number} p.row + * @param {String} p.fileFormat (see Format object) + * @param {String} p.dataTypeHint stuff like 'spectrum', 'image', 'cube', etc + * @param {DatalinkData} p.dlData + * @param {String} p.serverCacheFileKey + * @param {ActivateParams} p.activateParams + * @param {DataProductsFactoryOptions} p.options * @return {{tableResult: DataProductsDisplayType|Array.|undefined, imageResult: DataProductsDisplayType|undefined}} */ -export function analyzePart(part, request, table, row, fileFormat, dataTypeHint, serverCacheFileKey, activateParams, options) { +export function analyzePart({part, request, table, row, fileFormat, dataTypeHint, dlData, + serverCacheFileKey, activateParams, options}) { const {type,desc, fileLocationIndex}= part; const aTypes= findAvailableTypesForAnalysisPart(part, fileFormat); @@ -33,7 +37,8 @@ export function analyzePart(part, request, table, row, fileFormat, dataTypeHint, const fileOnServer= (part.convertedFileName) ? part.convertedFileName : serverCacheFileKey; const imageResult= aTypes.includes(DPtypes.IMAGE) && type===FileAnalysisType.Image && - analyzeImageResult(part, request, table, row, fileFormat, part.convertedFileName,desc,activateParams,fileLocationIndex); + analyzeImageResult({part, request, table, row, fileFormat, dlData, fileOnServer, + title:desc,activateParams,hduIdx:fileLocationIndex}); const tableResult= aTypes.includes(DPtypes.TABLE) && analyzeChartTableResult(false, table, row, part, fileFormat, fileOnServer,desc,dataTypeHint,activateParams,fileLocationIndex, options); @@ -319,7 +324,7 @@ function getPartProdGuess(part,table,row) { -function analyzeImageResult(part, request, table, row, fileFormat, fileOnServer,title='', activateParams, hduIdx) { +function analyzeImageResult({part, request, table, row, dlData, fileOnServer,title='', activateParams, hduIdx}) { const {interpretedData=false,uiEntry,uiRender, defaultPart=false}= part; if (uiEntry===UIEntry.UseSpecified && uiRender!==UIRender.Image) return undefined; const newReq= request.makeCopy(); @@ -338,9 +343,11 @@ function analyzeImageResult(part, request, table, row, fileFormat, fileOnServer, const ddTitleStr= (interpretedData || uiEntry===UIEntry.UseSpecified || fileOnServer) ? `${title} (image)` : `HDU #${hduIdx||0} (image) ${title}`; + const sourceObsCoreData= dlData ? dlData.sourceObsCoreData : getObsCoreData(table,row); + return dpdtImage({name:ddTitleStr, activate: createSingleImageActivate(newReq,imageViewerId,table?.tbl_id,row), - extraction: createSingleImageExtraction(newReq), + extraction: createSingleImageExtraction(newReq, sourceObsCoreData, dlData ), request:newReq, override, interpretedData, requestDefault:Boolean(defaultPart)}); } diff --git a/src/firefly/js/metaConvert/TableDataProductUtils.js b/src/firefly/js/metaConvert/TableDataProductUtils.js index b10807166..9f813ee37 100644 --- a/src/firefly/js/metaConvert/TableDataProductUtils.js +++ b/src/firefly/js/metaConvert/TableDataProductUtils.js @@ -17,6 +17,12 @@ import { import {getActiveTableId, getTblById, onTableLoaded} from '../tables/TableUtil'; import {getCellValue, getTblInfo} from '../tables/TableUtil.js'; import MultiViewCntlr, {dispatchUpdateCustom, getMultiViewRoot, getViewer} from '../visualize/MultiViewCntlr.js'; +import { + getObsCoreAccessURL, getSearchTarget, isFormatDataLink, makeWorldPtUsingCenterColumns +} from '../voAnalyzer/TableAnalysis'; +import {getServiceDescriptors} from '../voAnalyzer/VoDataLinkServDef'; +import {makeDlUrl} from './vo/DatalinkProducts'; +import {findDataLinkServeDescs} from './vo/ServDescConverter'; export function createTableActivate(source, titleStr, activateParams, dataTypeHint= '', tbl_index=0) { @@ -122,7 +128,17 @@ function loadTableAndCharts(dataTableReq, tbl_id, tableGroupViewerId, dispatchCh }; } -export function createTableExtraction(source,titleInfo,tbl_index,colNames,colUnits,cubePlane=0,dataTypeHint) { +/** + * + * @param source + * @param titleInfo + * @param [tbl_index] + * @param [colNames] + * @param [colUnits] + * @param [cubePlane] + * @param [dataTypeHint] + */ +export function createTableExtraction(source,titleInfo,tbl_index=0,colNames,colUnits,cubePlane=0,dataTypeHint) { return () => { const ti= isString(titleInfo) ? {titleStr:titleInfo} : titleInfo; const dataTableReq= makeTableRequest(source,ti,undefined,tbl_index,colNames,colUnits,cubePlane,dataTypeHint, true); @@ -132,6 +148,30 @@ export function createTableExtraction(source,titleInfo,tbl_index,colNames,colUni }; } +export function extractDatalinkTable(table,row,title,setAsActive=true) { + let url; + if (isFormatDataLink(table, row)) { + url= getObsCoreAccessURL(table,row); + } + else { + const serDefAry= getServiceDescriptors(table); + if (!serDefAry || !serDefAry.length) return; + const dlSerDef= findDataLinkServeDescs(serDefAry); + if (!dlSerDef) return; + url= makeDlUrl(dlSerDef[0],table,row); + } + if (!url) return; + + const positionWP = getSearchTarget(table?.request, table) ?? makeWorldPtUsingCenterColumns(table, row); + + + const dataTableReq= makeTableRequest(url,{titleStr:title},undefined,0,undefined,undefined,undefined,undefined,true); + if (positionWP) dataTableReq.META_INFO[MetaConst.SEARCH_TARGET]= positionWP.toString(); + + dispatchTableSearch(dataTableReq, {setAsActive, logHistory: false, showFilters: true, showInfoButton: true}); + showPinMessage('Pinning to Table Area'); +} + /** diff --git a/src/firefly/js/metaConvert/UploadAndAnalysis.js b/src/firefly/js/metaConvert/UploadAndAnalysis.js index 295126b5f..ef6aae93d 100644 --- a/src/firefly/js/metaConvert/UploadAndAnalysis.js +++ b/src/firefly/js/metaConvert/UploadAndAnalysis.js @@ -5,10 +5,11 @@ import {dispatchAddActionWatcher, dispatchCancelActionWatcher} from '../core/Mas import {DataProductTypes, FileAnalysisType} from '../data/FileAnalysis'; import {MetaConst} from '../data/MetaConst'; import {upload} from '../rpc/CoreServices.js'; -import {getMetaEntry, getTblRowAsObj} from '../tables/TableUtil'; +import {getMetaEntry, getTblById, getTblRowAsObj} from '../tables/TableUtil'; import {hashCode} from '../util/WebUtil'; import {isDefined} from '../util/WebUtil.js'; import ImagePlotCntlr from '../visualize/ImagePlotCntlr'; +import {getObsCoreData} from '../voAnalyzer/VoDataLinkServDef'; import { dataProductRoot, dispatchUpdateActiveKey, dispatchUpdateDataProducts, getActiveFileMenuKeyByKey, getDataProducts @@ -42,14 +43,16 @@ const parseAnalysis= (serverCacheFileKey, analysisResult) => * @param {DataProductsFactoryOptions} obj.options * @param {Array.} [obj.menu] * @param {ServiceDescriptorDef} [obj.serDef] + * @param {DatalinkData} [obj.dlData] * @param {Object} [obj.userInputParams] * @param {Function} [obj.analysisActivateFunc] * @param {string} [obj.originalTitle] * @param {string} [obj.menuKey] + * @param obj.serviceDescMenuList * @return {Promise.} */ export async function doUploadAndAnalysis({ table, row, request, activateParams={}, dataTypeHint='', options, menuKey, - menu, serDef, userInputParams, analysisActivateFunc, originalTitle,serviceDescMenuList}) { + menu, serDef, dlData, userInputParams, analysisActivateFunc, originalTitle,serviceDescMenuList}) { const {dpId}= activateParams; @@ -60,7 +63,7 @@ export async function doUploadAndAnalysis({ table, row, request, activateParams= fileAnalysis.fileName&&'Download File', request.getURL()); } const result= processAnalysisResult({ table, row, request, activateParams, serverCacheFileKey, - fileAnalysis, dataTypeHint, analysisActivateFunc, serDef, originalTitle, options, menuKey}); + fileAnalysis, dataTypeHint, analysisActivateFunc, serDef, dlData, originalTitle, options, menuKey}); if (serviceDescMenuList && result) { return makeSingleDataProductWithMenu(activateParams.dpId,result,1,serviceDescMenuList); } @@ -175,16 +178,19 @@ function makeErrorResult(message, fileName,url) { return dpdtMessageWithDownload(`No displayable data available for this row${message?': '+message:''}`, fileName&&'Download: '+fileName, url); } -function makeAllImageEntry(request, path, parts, imageViewerId, tbl_id, row, imagePartsLength) { +function makeAllImageEntry({request, path, parts, imageViewerId, dlData, tbl_id, row, imagePartsLength}) { const newReq= request.makeCopy(); newReq.setFileName(path); newReq.setRequestType(RequestType.FILE); parts.forEach( (p) => Object.entries(p.additionalImageParams ?? {} ) .forEach(([k,v]) => newReq.setParam(k,v))); const title= request.getTitle() || ''; + const sourceObsCoreData= dlData ? dlData.sourceObsCoreData : getObsCoreData(getTblById(tbl_id),row); return dpdtImage({name: `Show: ${title||'Image Data'} ${imagePartsLength>1? ': All Images in File' :''}`, + dlData, activate: createSingleImageActivate(newReq,imageViewerId,tbl_id,row), - extraction: createSingleImageExtraction(newReq), request}); + extraction: createSingleImageExtraction(newReq, sourceObsCoreData, dlData), + request}); } /** @@ -199,6 +205,7 @@ function makeAllImageEntry(request, path, parts, imageViewerId, tbl_id, row, im * @param {String} obj.dataTypeHint stuff like 'spectrum', 'image', 'cube', etc * @param {Function} obj.analysisActivateFunc * @param {ServiceDescriptorDef} obj.serDef + * @param {DatalinkData} [obj.dlData] * @param {String} obj.originalTitle * @param {DataProductsFactoryOptions} obj.options * @param {String} obj.menuKey @@ -206,7 +213,7 @@ function makeAllImageEntry(request, path, parts, imageViewerId, tbl_id, row, im */ function processAnalysisResult({table, row, request, activateParams, serverCacheFileKey, fileAnalysis, dataTypeHint, - analysisActivateFunc, serDef, originalTitle, options, menuKey}) { + analysisActivateFunc, serDef, dlData, originalTitle, options, menuKey}) { const {parts,fileName,fileFormat}= fileAnalysis; if (!parts) return makeErrorResult('',fileName,serverCacheFileKey); @@ -220,14 +227,14 @@ function processAnalysisResult({table, row, request, activateParams, return deeperInspection({ table, row, request, activateParams, serverCacheFileKey, fileAnalysis, dataTypeHint, - analysisActivateFunc, serDef, originalTitle, url, options + analysisActivateFunc, serDef, dlData, originalTitle, url, options }); } function deeperInspection({ table, row, request, activateParams, serverCacheFileKey, fileAnalysis, dataTypeHint, - analysisActivateFunc, serDef, originalTitle, url, options}) { + analysisActivateFunc, serDef, dlData, originalTitle, url, options}) { const {parts,fileFormat, disableAllImageOption= false}= fileAnalysis; const {imageViewerId, dpId}= activateParams; @@ -235,7 +242,9 @@ function deeperInspection({ table, row, request, activateParams, const activeItemLookupKey= hashCode(rStr); const fileMenu= {fileAnalysis, menu:[],activeItemLookupKey, activeItemLookupKeyOrigin:rStr}; - const partAnalysis= parts.map( (p) => analyzePart(p,request, table, row, fileFormat, dataTypeHint, serverCacheFileKey,activateParams, options)); + const partAnalysis= parts.map( (part) => + analyzePart({part,request, table, row, fileFormat, dataTypeHint, + dlData, serverCacheFileKey,activateParams, options})); const imageParts= partAnalysis.filter( (pa) => pa.imageResult); let makeAllImageOption= !disableAllImageOption; if (makeAllImageOption) makeAllImageOption= imageParts.length>1 || (imageParts.length===1 && parts.length===1); @@ -247,7 +256,8 @@ function deeperInspection({ table, row, request, activateParams, const imageEntry= makeAllImageOption && - makeAllImageEntry(request,fileAnalysis.filePath, parts,imageViewerId,table?.tbl_id,row,imageParts.length); + makeAllImageEntry({request,path:fileAnalysis.filePath, parts,imageViewerId, dlData, + tbl_id:table?.tbl_id,row,imagePartsLength:imageParts.length}); if (imageEntry) fileMenu.menu.push(imageEntry); partAnalysis.forEach( (pa) => { diff --git a/src/firefly/js/metaConvert/vo/DataLinkProcessor.js b/src/firefly/js/metaConvert/vo/DataLinkProcessor.js index 9124dd925..86be17b04 100644 --- a/src/firefly/js/metaConvert/vo/DataLinkProcessor.js +++ b/src/firefly/js/metaConvert/vo/DataLinkProcessor.js @@ -1,16 +1,16 @@ -import { - getObsCoreProdType, getObsCoreSRegion, getSearchTarget, makeWorldPtUsingCenterColumns -} from '../../voAnalyzer/TableAnalysis.js'; +import {getPreferCutout} from '../../ui/tap/ObsCoreOptions'; +import { getSearchTarget, makeWorldPtUsingCenterColumns } from '../../voAnalyzer/TableAnalysis.js'; import { getDataLinkData, isDownloadType, isGzipType, isSimpleImageType, isTarType, isVoTable } from '../../voAnalyzer/VoDataLinkServDef.js'; -import {GIG} from '../../util/WebUtil.js'; +import {getSizeAsString, GIG} from '../../util/WebUtil.js'; import {makeAnalysisActivateFunc} from '../AnalysisUtils.js'; -import {dispatchUpdateActiveKey, getActiveMenuKey, getCurrentActiveKeyID} from '../DataProductsCntlr.js'; +import { + DEFAULT_DATA_PRODUCTS_COMPONENT_KEY, dispatchUpdateActiveKey, getActiveMenuKey, getCurrentActiveKeyID +} from '../DataProductsCntlr.js'; import { dpdtAnalyze, dpdtChartTable, dpdtDownload, dpdtDownloadMenuItem, dpdtFromMenu, dpdtImage, dpdtMessage, - dpdtMessageWithError, dpdtPNG, - dpdtTable, DPtypes + dpdtMessageWithError, dpdtPNG, dpdtTable, DPtypes } from '../DataProductsType.js'; import {createSingleImageActivate, createSingleImageExtraction} from '../ImageDataProductsUtil.js'; import { @@ -24,6 +24,8 @@ export const USE_ALL= 'useAllAlgorithm'; export const RELATED_IMAGE_GRID= 'relatedImageGridAlgorithm'; export const IMAGE= 'imageAlgorithm'; export const SPECTRUM= 'spectrumAlgorithm'; +const MAX_SIZE= 2*GIG; +const WARN_SIZE= GIG; /** @@ -44,16 +46,18 @@ export const SPECTRUM= 'spectrumAlgorithm'; export function processDatalinkTable({sourceTable, row, datalinkTable, activateParams, baseTitle=undefined, additionalServiceDescMenuList, dlTableUrl, doFileAnalysis=true, options, parsingAlgorithm = USE_ALL}) { - const dataLinkData= getDataLinkData(datalinkTable); - const isImageGrid= options.allowImageRelatedGrid && dataLinkData.filter( (dl) => dl.dlAnalysis.isImage && dl.dlAnalysis.isGrid).length>1; + const dataLinkData= getDataLinkData(datalinkTable,sourceTable,row); + const {dataProductsComponentKey=DEFAULT_DATA_PRODUCTS_COMPONENT_KEY}= options; + const preferCutout= getPreferCutout(dataProductsComponentKey,sourceTable?.tbl_id); + const isImageGrid= options.allowImageRelatedGrid && dataLinkData.filter( (dl) => dl.dlAnalysis.isImage && dl.dlAnalysis.isGrid).length>1; const isMultiTableSpectrum= dataLinkData.filter( (dl) => dl.dlAnalysis.isThis && dl.dlAnalysis.isGrid && dl.dlAnalysis.isSpectrum).length>1; if (parsingAlgorithm===USE_ALL && isMultiTableSpectrum) parsingAlgorithm= SPECTRUM; // todo this is probably temporary for testing const menu= dataLinkData.length && createDataLinkMenuRet({dlTableUrl,dataLinkData,sourceTable, sourceRow:row, activateParams, baseTitle, - additionalServiceDescMenuList, doFileAnalysis, parsingAlgorithm, options}); + additionalServiceDescMenuList, doFileAnalysis, parsingAlgorithm, options, preferCutout}); - const canShow= menu.length>0 && menu.some( (m) => m.displayType!==DPtypes.DOWNLOAD && (!m.size || m.size0 && menu.some( (m) => m.displayType!==DPtypes.DOWNLOAD && (!m.size || m.size d.displayType); } -function getDLMenuEntryData({dlTableUrl, dlData,idx, sourceTable, sourceRow}) { - const positionWP= getSearchTarget(sourceTable?.request,sourceTable) ?? makeWorldPtUsingCenterColumns(sourceTable,sourceRow); - const sRegion= getObsCoreSRegion(sourceTable,sourceRow); - const prodType= getObsCoreProdType(sourceTable,sourceRow); - const contentType= dlData.contentType.toLowerCase(); - return {positionWP,contentType, sRegion,prodType, activeMenuLookupKey:dlTableUrl??`no-table-${idx}`,menuKey:'dlt-'+idx}; +function getDLMenuEntryData({dlTableUrl, dlData={}, idx, sourceTable, sourceRow}) { + return { + positionWP: getSearchTarget(sourceTable?.request,sourceTable) ?? makeWorldPtUsingCenterColumns(sourceTable,sourceRow), + contentType: dlData.contentType?.toLowerCase(), + sRegion: dlData.sourceObsCoreData?.s_region, + prodType: dlData.sourceObsCoreData?.dataproduct_type, + activeMenuLookupKey:dlTableUrl??`no-table-${idx}`, + menuKey:'dlt-'+idx + }; } function makeDLServerDefMenuEntry({dlTableUrl, dlData,idx, baseTitle, sourceTable, sourceRow, options, name, activateParams}) { - const {serDef, semantics,size,serviceDefRef,dlAnalysis}= dlData; - const {positionWP,sRegion,prodType, - activeMenuLookupKey,menuKey}= getDLMenuEntryData({dlTableUrl, dlData,idx,sourceTable,sourceRow}); - + const {serDef}= dlData; + const {positionWP, activeMenuLookupKey,menuKey}= getDLMenuEntryData({dlTableUrl, dlData,idx,sourceTable,sourceRow}); const {title:servDescTitle=''}= serDef; const titleStr= baseTitle ? `${baseTitle} (${dlData.description||servDescTitle})` : (dlData.description||servDescTitle); return makeServiceDefDataProduct({ serDef, sourceTable, sourceRow, idx, positionWP, activateParams, options, name, - titleStr, activeMenuLookupKey, menuKey, - datalinkExtra: {semantics, size, sRegion, prodTypeHint: dlData.contentType || prodType, serviceDefRef, dlAnalysis} + titleStr, activeMenuLookupKey, menuKey, dlData, }); } @@ -171,14 +174,14 @@ function makeDLAccessUrlMenuEntry({dlTableUrl, dlData,idx, sourceTable, sourceRo if (isTar) fileType= 'tar'; if (isGzip) fileType= 'gzip'; return isThis ? - dpdtDownloadMenuItem('Download file: '+name,url,menuKey,fileType,{semantics, size, activeMenuLookupKey}) : - dpdtDownload('Download file: '+name,url,menuKey,fileType,{semantics, size, activeMenuLookupKey}); + dpdtDownloadMenuItem('Download file: '+name,url,menuKey,fileType,{semantics, size, activeMenuLookupKey,dlData}) : + dpdtDownload('Download file: '+name,url,menuKey,fileType,{semantics, size, activeMenuLookupKey, dlData}); } else if (isSimpleImage) { - return dpdtPNG('Show PNG image: '+name,url,menuKey,{semantics, size, activeMenuLookupKey}); + return dpdtPNG('Show PNG image: '+name,url,menuKey,{semantics, size, activeMenuLookupKey, dlData}); } else if (isTooBig(size)) { - return dpdtDownload('Download: '+name + '(too large to show)',url,menuKey,'fits',{semantics, size, activeMenuLookupKey}); + return dpdtDownload('Download: '+name + '(too large to show)',url,menuKey,'fits',{semantics, size, activeMenuLookupKey, dlData}); } else if (dlData.dlAnalysis.isSpectrum && isVoTable(contentType)) { const tbl_id= getTableId(dlData.description,options,idx); @@ -193,7 +196,7 @@ function makeDLAccessUrlMenuEntry({dlTableUrl, dlData,idx, sourceTable, sourceRo chartId, }); const extract= createTableExtraction(url,description,0); - return dpdtChartTable('Show: ' + description, activate, extract, menuKey, {extractionText: 'Pin Table', paIdx:0, tbl_id,chartId}); + return dpdtChartTable('Show: ' + description, activate, extract, menuKey, {extractionText: 'Pin Table', paIdx:0, tbl_id,chartId, dlData}); } else if (isAnalysisType(contentType)) { if (doFileAnalysis) { @@ -201,12 +204,12 @@ function makeDLAccessUrlMenuEntry({dlTableUrl, dlData,idx, sourceTable, sourceRo const prodTypeHint= dlData.dlAnalysis.isSpectrum ? 'spectrum' : (dlData.contentType || prodType); const request= makeObsCoreRequest(url,positionWP,name,sourceTable,sourceRow); const activate= makeAnalysisActivateFunc({table:sourceTable,row:sourceRow, request, - activateParams,menuKey, dataTypeHint, options}); + activateParams,menuKey, dataTypeHint, options, dlData}); return dpdtAnalyze({name:'Show: '+name, - activate,url,menuKey, semantics, size, activeMenuLookupKey,request, sRegion, prodTypeHint}); + activate,url,menuKey, semantics, size, activeMenuLookupKey,request, sRegion, prodTypeHint, dlData}); } else { - return createGuessDataType(name,menuKey,url,contentType,semantics, activateParams, positionWP,sourceTable,sourceRow,size); + return createGuessDataType(name,menuKey,url,contentType,semantics, activateParams, positionWP,sourceTable,sourceRow,size, dlData); } } } @@ -253,15 +256,19 @@ function makeMenuEntry({dlTableUrl, dlData,idx, baseTitle, sourceTable, sourceRo * * @param parsingAlgorithm * @param {Array.} dataLinkData + * @param {boolean} preferCutout * @return {Array.} */ -export function filterDLList(parsingAlgorithm, dataLinkData) { +export function filterDLList(parsingAlgorithm, dataLinkData, preferCutout) { if (parsingAlgorithm===USE_ALL) return dataLinkData; if (parsingAlgorithm===IMAGE) { return dataLinkData.filter( ({dlAnalysis}) => dlAnalysis.isImage); } if (parsingAlgorithm===RELATED_IMAGE_GRID) { - return dataLinkData.filter( ({dlAnalysis}) => dlAnalysis.isGrid && dlAnalysis.isImage); + const relatedGrid= dataLinkData.filter( ({dlAnalysis}) => dlAnalysis.isGrid && dlAnalysis.maybeImage); + + return relatedGrid.filter( (g) => ( + g.dlAnalysis.cutoutFullPair && !g.dlAnalysis.isCutout) || !g.dlAnalysis.cutoutFullPair); } if (parsingAlgorithm===SPECTRUM) { return dataLinkData.filter( ({dlAnalysis}) => dlAnalysis.isSpectrum); @@ -272,9 +279,14 @@ export function filterDLList(parsingAlgorithm, dataLinkData) { function sortMenu(menu) { return menu - .sort(({semantics:sem1,name:n1},{semantics:sem2,name:n2}) => { - if (isThisSem(sem1)) { - if (isThisSem(sem2)) { + .sort( (m1,m2) => { + const isThis1= m1.dlData?.dlAnalysis?.isThis ?? false; + const isThis2= m2.dlData?.dlAnalysis?.isThis ?? false; + const n1= m1.name; + const n2= m2.name; + + if (isThis1) { + if (isThis2) { if (n1?.includes('(#this)')) return -1; else if (n2?.includes('(#this)')) return 1; else if (n1} [obj.additionalServiceDescMenuList] * @param obj.doFileAnalysis + * @param obj.preferCutout * @param {DataProductsFactoryOptions} obj.options * @param obj.parsingAlgorithm * @param obj.baseTitle * @return {Array.} */ function createDataLinkMenuRet({dlTableUrl, dataLinkData, sourceTable, sourceRow, activateParams, baseTitle, - additionalServiceDescMenuList=[], doFileAnalysis=true, + additionalServiceDescMenuList=[], doFileAnalysis=true, preferCutout, options, parsingAlgorithm=USE_ALL}) { const auxTot= dataLinkData.filter( (e) => e.semantics==='#auxiliary').length; let auxCnt=0; let primeCnt=0; - const menu= filterDLList(parsingAlgorithm,dataLinkData) + const menu= filterDLList(parsingAlgorithm,dataLinkData, preferCutout) .map( (dlData) => { - const {semantics,url,error_message, dlAnalysis:{isAux,isThis}}= dlData; - const name= makeName(semantics, url, auxTot, auxCnt, primeCnt, baseTitle); + const {url,error_message, + dlAnalysis:{isAux,isThis,cutoutFullPair,isCounterpart,isCutout}}= dlData; + const idx= dlData.rowIdx; + const name= makeName(dlData, url, auxTot, auxCnt, primeCnt, baseTitle); if (error_message) { const edp= dpdtMessageWithError(error_message); edp.complexMessage= false; - edp.menuKey='dlt-'+dlData.rowIdx; + edp.menuKey='dlt-'+idx; edp.name= `Error in related data (datalink) row ${dlData.rowIdx}`; return edp; } - const menuEntry= makeMenuEntry({dlTableUrl,dlData,idx:dlData.rowIdx, baseTitle, sourceTable, - sourceRow, options, name, doFileAnalysis, activateParams}); + + const menuParams= {dlTableUrl,dlData,idx, baseTitle, sourceTable, + sourceRow, options, name, doFileAnalysis, activateParams}; + + if (cutoutFullPair) { + if (isCutout) return; + if (preferCutout && (isThis || isCounterpart)) { + if (isWarnSize(dlData.size)) { + dlData.relatedDLEntries.cutout.cutoutToFullWarning= + `Warning: Full image file is ${getSizeAsString(dlData.size)}, it might take awhile to load`; + } + menuParams.dlData = dlData.relatedDLEntries.cutout; + } + } + const menuEntry= makeMenuEntry(menuParams); if (isAux) auxCnt++; if (isThis) primeCnt++; return menuEntry; }) - .filter((menuEntry) => menuEntry); + .filter(Boolean); if (parsingAlgorithm===SPECTRUM && menu.length>1) { @@ -344,14 +372,14 @@ function createDataLinkMenuRet({dlTableUrl, dataLinkData, sourceTable, sourceRow } if (parsingAlgorithm===USE_ALL) { - menu.push(...additionalServiceDescMenuList,...addDataLinkEntries(dlTableUrl,activateParams)); + menu.push(...additionalServiceDescMenuList); } return sortMenu(menu); } export function createDataLinkSingleRowItem({dlData, activateParams, baseTitle, options}) { - const {semantics,url,error_message, dlAnalysis:{isAux,isThis}, serDef, serviceDefRef}= dlData; + const {semantics,error_message, serDef, serviceDefRef}= dlData; const name= semantics; if (error_message) { const edp= dpdtMessageWithError(error_message); @@ -377,22 +405,55 @@ export function createDataLinkSingleRowItem({dlData, activateParams, baseTitle, const analysisTypes= ['fits', 'cube', 'table', 'spectrum', 'auxiliary']; -function makeName(s='', url, auxTot, autCnt, primeCnt=0, baseTitle) { - if (baseTitle) return makeNameWithBaseTitle(s,auxTot,autCnt,primeCnt,baseTitle); - let name= (s==='#this' && primeCnt>0) ? '#this '+primeCnt : s; - name= s.startsWith('#this') ? `Primary product (${name})` : s; - name= name[0]==='#' ? name.substring(1) : name; - name= (name==='auxiliary' && auxTot>1) ? `${name}: ${autCnt}` : name; - return name || url; +function makeName(dlData, url, auxTot, autCnt, primeCnt=0, baseTitle) { + const {id,semantics,dlAnalysis:{isThis,isAux}}= dlData; + if (baseTitle) return makeNameWithBaseTitle(dlData,auxTot,autCnt,primeCnt,baseTitle); + const baseTitleFromId= getBaseTitleFromId(id); + let name= semantics[0]==='#' ? semantics.substring(1) : name; + if (baseTitleFromId) { + if (isThis) return `${baseTitleFromId}`; + if (isAux) return `${baseTitleFromId}: auxiliary ${auxTot>1?autCnt+'':''}`; + else return `${baseTitleFromId}: ${name}`; + } + else { + name= (isThis && primeCnt>0) ? '#this '+primeCnt : name; + name= isThis ? `Primary product (${name})` : name; + name= (isAux && auxTot>1) ? `${name}: ${autCnt}` : name; + return name || url; + } } -function makeNameWithBaseTitle(s='', auxTot, autCnt, primeCnt=0, baseTitle) { - if (!s) return baseTitle; - if (s.startsWith('#this')) { +function getBaseTitleFromId(id) { + if (!id?.toLowerCase().startsWith('ivo:')) return; + try { + const url= new URL(id); + if (!url) return; + const sp= url.searchParams; + if (sp.size) { + const keyNames= [...sp.keys()]; + if (keyNames.length===1) return keyNames[0]; + return; + } + if (url.pathname.length>1) { + return url.pathname.substring(1); + } + } + catch { + // do nothing + } + + +} + + +function makeNameWithBaseTitle(dlData, auxTot, autCnt, primeCnt=0, baseTitle) { + const {semantics,dlAnalysis:{isThis,isAux}}= dlData; + if (!semantics) return baseTitle; + if (isThis) { return primeCnt<1 ? `${baseTitle} (#this)` : `${baseTitle} (#this ${primeCnt})`; } - if (s==='auxiliary' || s==='#auxiliary') return `auxiliary${auxTot>0?' '+autCnt:''}: ${baseTitle}`; - return s[0]==='#' ? `${s.substring(1)}: ${baseTitle}` : `${s}: ${baseTitle}`; + if (isAux) return `auxiliary${auxTot>0?' '+autCnt:''}: ${baseTitle}`; + return semantics[0]==='#' ? `${semantics.substring(1)}: ${baseTitle}` : `${semantics}: ${baseTitle}`; } @@ -408,37 +469,37 @@ function makeNameWithBaseTitle(s='', auxTot, autCnt, primeCnt=0, baseTitle) { * @param table * @param row * @param size + * @param dlData * @return {DataProductsDisplayType} */ -export function createGuessDataType(name, menuKey, url,ct,semantics, activateParams, positionWP, table,row,size) { +export function createGuessDataType(name, menuKey, url,ct,semantics, activateParams, positionWP, table,row,size,dlData) { const {imageViewerId}= activateParams; if (ct.includes('image') || ct.includes('fits') || ct.includes('cube')) { const request= makeObsCoreRequest(url,positionWP,name,table,row); return dpdtImage({name, activate: createSingleImageActivate(request,imageViewerId,table.tbl_id,row), - extraction: createSingleImageExtraction(request), - menuKey, request,url, semantics,size}); + extraction: createSingleImageExtraction(request, dlData?.sourceObsCoreData, dlData), + menuKey, request,url, semantics,size,dlData}); } else if (ct.includes('table') || ct.includes('spectrum') || semantics.includes('auxiliary')) { return dpdtTable(name, createTableActivate(url, semantics, activateParams, ct), - menuKey,{url,semantics,size} ); + menuKey,{url,semantics,size,dlData} ); } else if (isSimpleImageType(ct)) { - return dpdtPNG(name,url,menuKey,{semantics}); + return dpdtPNG(name,url,menuKey,{semantics,dlData}); } else if (isDownloadType(ct)) { let fileType; if (isTarType(ct)) fileType= 'tar'; if (isGzipType('gz')) fileType= 'gzip'; - return dpdtDownload(name,url,menuKey,fileType,{semantics}); + return dpdtDownload(name,url,menuKey,fileType,{semantics,dlData}); } } - -const isThisSem= (semantics) => semantics==='#this'; const isAnalysisType= (ct) => (ct==='' || analysisTypes.some( (a) => ct.includes(a))); -const isTooBig= (size) => size>GIG; +const isTooBig= (size) => size>MAX_SIZE; +export const isWarnSize= (size) => size>WARN_SIZE; diff --git a/src/firefly/js/metaConvert/vo/DataLinkStandAloneConverter.js b/src/firefly/js/metaConvert/vo/DataLinkStandAloneConverter.js index 1f90e7a64..f1d5dca4c 100644 --- a/src/firefly/js/metaConvert/vo/DataLinkStandAloneConverter.js +++ b/src/firefly/js/metaConvert/vo/DataLinkStandAloneConverter.js @@ -1,4 +1,6 @@ +import {getPreferCutout} from '../../ui/tap/ObsCoreOptions'; import {getDataLinkData} from '../../voAnalyzer/VoDataLinkServDef.js'; +import {DEFAULT_DATA_PRODUCTS_COMPONENT_KEY} from '../DataProductsCntlr'; import {createDataLinkSingleRowItem} from './DataLinkProcessor.js'; /** @@ -16,8 +18,19 @@ export function makeDatalinkStaneAloneConverter(table,converterTemplate,options= export async function getDatalinkStandAlineDataProduct(table, row, activateParams, options) { const dataLinkData= getDataLinkData(table); - const dlData= dataLinkData?.[row]; + const {dataProductsComponentKey=DEFAULT_DATA_PRODUCTS_COMPONENT_KEY}= options; + const preferCutout= getPreferCutout(dataProductsComponentKey,table?.tbl_id); - const item= createDataLinkSingleRowItem({dlData, activateParams, baseTitle:'Datalink data', options}); - return item; + let dlData= dataLinkData?.[row]; + const {isCutout,cutoutFullPair}= dlData.dlAnalysis; + + if (cutoutFullPair) { + if (preferCutout) { + dlData = isCutout ? dlData : dlData.relatedDLEntries.cutout; + } + else { + dlData = !isCutout ? dlData : dlData.relatedDLEntries.fullImage; + } + } + return createDataLinkSingleRowItem({dlData, activateParams, baseTitle:'Datalink data', options}); } \ No newline at end of file diff --git a/src/firefly/js/metaConvert/vo/DatalinkFetch.js b/src/firefly/js/metaConvert/vo/DatalinkFetch.js index 2f9579fb8..8732a690f 100644 --- a/src/firefly/js/metaConvert/vo/DatalinkFetch.js +++ b/src/firefly/js/metaConvert/vo/DatalinkFetch.js @@ -1,4 +1,5 @@ -import {makeFileRequest} from '../../tables/TableRequestUtil.js'; +import {isEmpty} from 'lodash'; +import {cloneRequest, makeFileRequest} from '../../tables/TableRequestUtil.js'; import {doFetchTable} from '../../tables/TableUtil.js'; let dlTableCache = new Map(); @@ -21,12 +22,10 @@ function cacheGet(url) { const cacheSet = (url, table) => dlTableCache.set(url, {time: Date.now(), table}); //todo - make version of this that supports concurrent all of same url, with on fetch -export async function fetchDatalinkTable(url) { +export async function fetchDatalinkTable(url, requestOptions={}) { const tableFromCache = cacheGet(url); if (tableFromCache) return tableFromCache; - // const request = makeFileRequest('dl table', url); - // const table = await doFetchTable(request); - const table= await doMultRequestTableFetch(url); + const table= await doMultRequestTableFetch(url, requestOptions); cacheSet(url, table); cacheCleanup(); return table; @@ -44,40 +43,46 @@ const waitingResolvers= new Map(); const waitingRejectors= new Map(); const LOAD_ERR_MSG='table retrieval fail, unknown reason '; -function clearAll(url) { - loadBegin.delete(url); - waitingResolvers.delete(url); - waitingRejectors.delete(url); +function clearAll(fetchKey) { + loadBegin.delete(fetchKey); + waitingResolvers.delete(fetchKey); + waitingRejectors.delete(fetchKey); } +function resolveAll(fetchKey, table) { + (waitingResolvers.get(fetchKey)??[]).forEach((r) => r(table)); + clearAll(fetchKey); +} + +function rejectAll(fetchKey, error) { + (waitingRejectors.get(fetchKey)??[]).forEach( (r) => r(error)); + clearAll(fetchKey); +} + + /** * This function supports doing a table fetch with the same url concurrently while only make one call the the server * @param url * @return {Promise} */ -async function doMultRequestTableFetch(url) { - - if (!waitingResolvers.has(url)) waitingResolvers.set(url,[]); - if (!waitingRejectors.has(url)) waitingRejectors.set(url,[]); - - if (!loadBegin.get(url)) { - loadBegin.set(url,true); - const request = makeFileRequest('dl table', url); - doFetchTable(request).then( (table) => { - if (table) { - (waitingResolvers.get(url)??[]).forEach((r) => r(table)); - } else { - (waitingRejectors.get(url)??[]).forEach( (r) => r(Error(LOAD_ERR_MSG))); - } - clearAll(url); - }).catch( (err) => { - (waitingRejectors.get(url)??[]).forEach( (r) => r(err)); - clearAll(url); - }); +async function doMultRequestTableFetch(url, requestOptions) { + + + const fetchKey= isEmpty(requestOptions) ? url : url+'--' + JSON.stringify(requestOptions); + + if (!waitingResolvers.has(fetchKey)) waitingResolvers.set(fetchKey,[]); + if (!waitingRejectors.has(fetchKey)) waitingRejectors.set(fetchKey,[]); + + if (!loadBegin.get(fetchKey)) { + loadBegin.set(fetchKey, true); + const request = cloneRequest(makeFileRequest('dl table', url), requestOptions); + doFetchTable(request) + .then((table) => table ? resolveAll(fetchKey, table) : rejectAll(fetchKey, Error(LOAD_ERR_MSG))) + .catch((err) => rejectAll(fetchKey, err)); } return new Promise( function(resolve, reject) { - waitingResolvers.get(url).push(resolve); - waitingRejectors.get(url).push(reject); + waitingResolvers.get(fetchKey).push(resolve); + waitingRejectors.get(fetchKey).push(reject); }); -}; +} diff --git a/src/firefly/js/metaConvert/vo/DatalinkProducts.js b/src/firefly/js/metaConvert/vo/DatalinkProducts.js index efe14a5c0..ed31f8d64 100644 --- a/src/firefly/js/metaConvert/vo/DatalinkProducts.js +++ b/src/firefly/js/metaConvert/vo/DatalinkProducts.js @@ -1,16 +1,16 @@ import {getCellValue} from '../../tables/TableUtil.js'; +import {getPreferCutout} from '../../ui/tap/ObsCoreOptions'; +import {getSizeAsString} from '../../util/WebUtil'; import {getDataLinkData} from '../../voAnalyzer/VoDataLinkServDef.js'; import {Band} from '../../visualize/Band.js'; import {WPConst} from '../../visualize/WebPlotRequest.js'; -import {dpdtImage, dpdtMessageWithDownload, dpdtSimpleMsg, DPtypes} from '../DataProductsType.js'; +import {DEFAULT_DATA_PRODUCTS_COMPONENT_KEY, dispatchUpdateDataProducts} from '../DataProductsCntlr'; +import {dpdtImage, dpdtMessageWithDownload, dpdtSimpleMsg, dpdtWorkingMessage, DPtypes} from '../DataProductsType.js'; import { createGridImagesActivate, createRelatedGridImagesActivate, createSingleImageExtraction } from '../ImageDataProductsUtil.js'; import {fetchDatalinkTable} from './DatalinkFetch.js'; -import { - filterDLList, - IMAGE, processDatalinkTable, RELATED_IMAGE_GRID, SPECTRUM, USE_ALL -} from './DataLinkProcessor.js'; +import { filterDLList, IMAGE, isWarnSize, processDatalinkTable, RELATED_IMAGE_GRID, SPECTRUM, USE_ALL } from './DataLinkProcessor.js'; /** @@ -27,16 +27,20 @@ import { */ export async function getDatalinkRelatedGridProduct({dlTableUrl, activateParams, table, row, threeColorOps, titleStr, options}) { try { - const datalinkTable = await fetchDatalinkTable(dlTableUrl); + dispatchUpdateDataProducts(activateParams.dpId, dpdtWorkingMessage('Loading data products...', 'working')); + const datalinkTable = await fetchDatalinkTable(dlTableUrl, options.datalinkTblRequestOptions); + const {dataProductsComponentKey=DEFAULT_DATA_PRODUCTS_COMPONENT_KEY}= options; + const preferCutout= getPreferCutout(dataProductsComponentKey,table?.tbl_id); - const gridData = getDataLinkData(datalinkTable).filter(({dlAnalysis}) => dlAnalysis.isGrid && dlAnalysis.isImage); + const gridData = getDataLinkData(datalinkTable,table,row).filter(({dlAnalysis}) => dlAnalysis.isGrid && dlAnalysis.isImage); if (!gridData.length) return dpdtSimpleMsg('no support for related grid in datalink file'); + const cutoutSwitching= dataSupportsCutoutSwitching(gridData); const dataLinkGrid = processDatalinkTable({ sourceTable: table, row, datalinkTable, activateParams, baseTitle: titleStr, dlTableUrl, doFileAnalysis: false, - options, parsingAlgorithm: RELATED_IMAGE_GRID + options, parsingAlgorithm: RELATED_IMAGE_GRID, }); @@ -47,6 +51,16 @@ export async function getDatalinkRelatedGridProduct({dlTableUrl, activateParams, result.displayType === DPtypes.PROMISE || result.displayType === DPtypes.ANALYZE)) .map((result) => result.request); + + const dlDataAry = dataLinkGrid.menu + .filter((result) => result?.dlData && ( + result.displayType === DPtypes.IMAGE || + result.displayType === DPtypes.PROMISE || + result.displayType === DPtypes.ANALYZE)) + .map((result) => result.dlData); + + const extractItems = dataLinkGrid.menu.filter((result) => result.displayType === DPtypes.EXTRACT); + requestAry.forEach((r, idx) => r.setPlotId(r.getPlotId() + '-related_grid-' + idx)); @@ -54,14 +68,31 @@ export async function getDatalinkRelatedGridProduct({dlTableUrl, activateParams, make3ColorRequestAry(requestAry,threeColorOps,datalinkTable.tbl_id); const activate = createRelatedGridImagesActivate({requestAry, threeColorReqAry, imageViewerId, tbl_id:table.tbl_id}); const extraction = createSingleImageExtraction(requestAry); - return dpdtImage({name:'image grid', activate, extraction, - enableCutout:true, + + const gridDlData= {...dlDataAry[0]}; + if (preferCutout) { + const allSize= dlDataAry.map ( (d) => d.size).reduce((tot,v) => tot+v,0) ; + if (isWarnSize(allSize)) { + gridDlData.cutoutToFullWarning= + `Warning: Loading ${requestAry.length} images with a total size of ${getSizeAsString(allSize)}, it might take awhile to load`; + } + } + + + const item= dpdtImage({name:'image grid', activate, extraction, + dlData: cutoutSwitching ? gridDlData : undefined, + enableCutout:preferCutout, + menu: extractItems, menuKey:'image-grid-0',serDef:dataLinkGrid.serDef}); + item.menu= extractItems; + return item; } catch (reason) { return dpdtMessageWithDownload(`No data to display: Could not retrieve datalink data, ${reason}`, 'Download File: ' + titleStr, dlTableUrl); } } +const dataSupportsCutoutSwitching= (gridData) => gridData.every( (dl) => dl.dlAnalysis.cutoutFullPair); + function make3ColorRequestAry(requestAry,threeColorOps,tbl_id) { const plotId= `3id_${tbl_id}`; return [ @@ -93,7 +124,8 @@ export async function getDatalinkSingleDataProduct({ dlTableUrl, doFileAnalysis = true, additionalServiceDescMenuList }) { try { - const datalinkTable = await fetchDatalinkTable(dlTableUrl); + dispatchUpdateDataProducts(activateParams.dpId, dpdtWorkingMessage('Loading data products...', 'working')); + const datalinkTable = await fetchDatalinkTable(dlTableUrl, options.datalinkTblRequestOptions); let parsingAlgorithm = USE_ALL; if (options.singleViewImageOnly) parsingAlgorithm = IMAGE; if (options.singleViewTableOnly) parsingAlgorithm = SPECTRUM; @@ -126,7 +158,7 @@ export async function createGridResult(promiseAry, activateParams, table, plotRo export async function datalinkDescribeThreeColor(dlTableUrl, table,row, options) { - const datalinkTable = await fetchDatalinkTable(dlTableUrl); + const datalinkTable = await fetchDatalinkTable(dlTableUrl, options.datalinkTblRequestOptions); const dataLinkGrid = processDatalinkTable({ sourceTable: table, row, datalinkTable, activateParams:{}, baseTitle: '', dlTableUrl, doFileAnalysis: false, diff --git a/src/firefly/js/metaConvert/vo/ObsCoreConverter.js b/src/firefly/js/metaConvert/vo/ObsCoreConverter.js index 528b7e5d3..ed1ad44f1 100644 --- a/src/firefly/js/metaConvert/vo/ObsCoreConverter.js +++ b/src/firefly/js/metaConvert/vo/ObsCoreConverter.js @@ -1,4 +1,4 @@ -import {isEmpty} from 'lodash'; +import {isEmpty, isUndefined} from 'lodash'; import {getAppOptions} from '../../core/AppDataCntlr.js'; import {getCellValue, getColumns, hasRowAccess} from '../../tables/TableUtil.js'; import { @@ -34,38 +34,49 @@ const DEF_MAX_PLOTS= 8; export function makeObsCoreConverter(table,converterTemplate,options={}) { if (!table) return converterTemplate; const canRelatedGrid= options.allowImageRelatedGrid?? false; + let canGrid= options.canGrid; + if (isUndefined(canGrid) && canRelatedGrid) { + canGrid= true; + } const threeColor= converterTemplate.threeColor && options?.allowImageRelatedGrid; - const baseRetOb= {...converterTemplate, + const onlyImages= hasOnlyImages(table); + + return { + ...converterTemplate, initialLayout: options.dataLinkInitialLayout ?? 'single', - describeThreeColor: (threeColor) ? describeObsThreeColor : undefined, + describeThreeColor: threeColor ? describeObsThreeColor : undefined, threeColor, - canGrid: false, - maxPlots:canRelatedGrid?DEF_MAX_PLOTS:1, - hasRelatedBands:canRelatedGrid, - converterId: `ObsCore-${table.tbl_id}`}; + canGrid: onlyImages && canGrid, + maxPlots: (canRelatedGrid||onlyImages) ? DEF_MAX_PLOTS : 1, + hasRelatedBands: canRelatedGrid, + converterId: `ObsCore-${table.tbl_id}` + }; +} +function hasOnlyImages(table) { + if (!table) return false; const propTypeCol= getObsCoreProdTypeCol(table); if (propTypeCol?.enumVals) { const pTypes= propTypeCol.enumVals.split(','); - if (pTypes.every( (s) => s.toLowerCase()==='image' || s.toLowerCase()==='cube')) { - return {...baseRetOb, canGrid:true,maxPlots:DEF_MAX_PLOTS}; - } + if (pTypes.every( (s) => s.toLowerCase()==='image' || s.toLowerCase()==='cube')) return true; } - if (table?.request?.filters) { + if (table.request?.filters) { const fList= table.request.filters.split(';'); const pTFilter= fList.find( (f) => f.includes(propTypeCol.name) && f.includes('IN')); if (pTFilter) { const inList= pTFilter.substring( pTFilter.indexOf('(')+1, pTFilter.indexOf(')')).split(','); if (inList.every( (s) => s.toLocaleLowerCase()==='\'image\'' || s.toLocaleLowerCase()==='\'cube\'')) { - return {...baseRetOb, canGrid:true,maxPlots:DEF_MAX_PLOTS}; + return true; } } } - return baseRetOb; + return false; } + + function describeObsThreeColor(table, row, options) { const {dataSource:dlTableUrl,prodType,isVoTable,isDataLinkRow, isPng}= getObsCoreRowMetaInfo(table,row); const errMsg= doErrorChecks(table,row,prodType,dlTableUrl,isDataLinkRow,isVoTable); diff --git a/src/firefly/js/metaConvert/vo/ServDescConverter.js b/src/firefly/js/metaConvert/vo/ServDescConverter.js index c7f15a990..30d0ac2d4 100644 --- a/src/firefly/js/metaConvert/vo/ServDescConverter.js +++ b/src/firefly/js/metaConvert/vo/ServDescConverter.js @@ -27,14 +27,13 @@ export function makeServDescriptorConverter(table,converterTemplate,options={}) const canRelatedGrid= options.allowImageRelatedGrid?? false; const threeColor= converterTemplate.threeColor && options?.allowImageRelatedGrid; - const allowServiceDefGrid= options.allowServiceDefGrid?? false; //------ const baseRetOb = { ...converterTemplate, initialLayout: options?.dataLinkInitialLayout ?? 'single', describeThreeColor: (threeColor) ? describeServDefThreeColor : undefined, threeColor, - canGrid: canRelatedGrid || allowServiceDefGrid, + canGrid: canRelatedGrid, maxPlots: canRelatedGrid ? DEF_MAX_PLOTS : 1, hasRelatedBands: canRelatedGrid, converterId: `ServiceDef-${table.tbl_id}` diff --git a/src/firefly/js/metaConvert/vo/ServDescProducts.js b/src/firefly/js/metaConvert/vo/ServDescProducts.js index c272e0e62..dde9bd524 100644 --- a/src/firefly/js/metaConvert/vo/ServDescProducts.js +++ b/src/firefly/js/metaConvert/vo/ServDescProducts.js @@ -1,13 +1,14 @@ import {isEmpty, isNumber} from 'lodash'; -import {dispatchComponentStateChange, getComponentState} from '../../core/ComponentCntlr.js'; +import {getComponentState} from '../../core/ComponentCntlr.js'; import {getCellValue, getColumnByRef} from '../../tables/TableUtil.js'; import {makeCircleString} from '../../ui/dynamic/DynamicUISearchPanel'; import {isSIAStandardID} from '../../ui/dynamic/ServiceDefTools'; +import {findCutoutTarget, getCutoutSize, getCutoutTargetOverride, setCutoutSize} from '../../ui/tap/ObsCoreOptions'; import {makeWorldPt} from '../../visualize/Point'; import {isDatalinkTable, isObsCoreLike} from '../../voAnalyzer/TableAnalysis'; import {CUTOUT_UCDs, DEC_UCDs, RA_UCDs} from '../../voAnalyzer/VoConst'; -import {isDataLinkServiceDesc} from '../../voAnalyzer/VoDataLinkServDef.js'; +import {findWorldPtInServiceDef, isDataLinkServiceDesc} from '../../voAnalyzer/VoDataLinkServDef.js'; import {isDefined} from '../../util/WebUtil.js'; import {makeAnalysisActivateFunc} from '../AnalysisUtils.js'; import {DEFAULT_DATA_PRODUCTS_COMPONENT_KEY} from '../DataProductsCntlr.js'; @@ -17,7 +18,6 @@ import {getObsCoreRowMetaInfo} from './ObsCoreConverter'; import {makeObsCoreRequest} from './VORequest.js'; -export const SD_CUTOUT_KEY= 'sdCutoutSize'; export const SD_DEFAULT_SPACIAL_CUTOUT_SIZE= .01; export const SD_DEFAULT_PIXEL_CUTOUT_SIZE= 200; @@ -35,43 +35,42 @@ export const SD_DEFAULT_PIXEL_CUTOUT_SIZE= 200; * @param p.titleStr * @param p.activeMenuLookupKey * @param p.menuKey - * @param [p.datalinkExtra] * @return {DataProductsDisplayType} */ export function makeServiceDefDataProduct({ name, serDef, sourceTable, sourceRow, idx, positionWP, activateParams, - options, titleStr, activeMenuLookupKey, menuKey, - datalinkExtra = {} }) { + options, titleStr, activeMenuLookupKey, menuKey, dlData={}}) { const {title: servDescTitle = '', accessURL, standardID, serDefParams, ID} = serDef; const {activateServiceDef=false}= options; const allowsInput = serDefParams.some((p) => p.allowsInput); const noInputRequired = serDefParams.some((p) => !p.inputRequired); - const {semantics, size, sRegion, prodTypeHint, serviceDefRef, dlAnalysis} = datalinkExtra; + const {semantics, size, serviceDefRef, dlAnalysis, prodTypeHint} = dlData; + const sRegion= dlData.sourceObsCoreData?.s_region ?? ''; - if (dlAnalysis?.isCutout && canMakeCutoutProduct(serDef,positionWP,sourceRow)) { + if (dlAnalysis?.isCutout && canMakeCutoutProduct(serDef,sourceTable,sourceRow,options)) { return makeCutoutProduct({ - name, serDef, sourceTable, sourceRow, idx, positionWP, activateParams, - options, titleStr, activeMenuLookupKey, menuKey, datalinkExtra + name, serDef, sourceTable, sourceRow, idx, activateParams, + options, titleStr, activeMenuLookupKey, menuKey, dlData, }); } else if (activateServiceDef && noInputRequired) { const url= makeUrlFromParams(accessURL, serDef, idx, getComponentInputs(serDef,options)); const request = makeObsCoreRequest(url, positionWP, titleStr, sourceTable, sourceRow); const activate = makeAnalysisActivateFunc({table:sourceTable, row:sourceRow, request, activateParams, - menuKey, dataTypeHint:prodTypeHint, serDef, options}); + menuKey, dataTypeHint:prodTypeHint, serDef, dlData, options}); return dpdtAnalyze({ - name:'Show: ' + (titleStr || name), activate, url:request.getURL(), serDef, menuKey, + name:'Show: ' + (titleStr || name), activate, url:request.getURL(), serDef, menuKey, dlData, activeMenuLookupKey, request, sRegion, prodTypeHint, semantics, size, serviceDefRef}); } else { const request = makeObsCoreRequest(accessURL, positionWP, titleStr, sourceTable, sourceRow); const activate = makeAnalysisActivateFunc({table:sourceTable, row:sourceRow, request, activateParams, menuKey, - dataTypeHint:prodTypeHint ?? 'unknown', serDef, originalTitle:name,options}); + dlData, dataTypeHint:prodTypeHint ?? 'unknown', serDef, originalTitle:name,options}); const entryName = `Show: ${titleStr || servDescTitle || `Service #${idx}: ${name}`} ${allowsInput ? ' (Input Required)' : ''}`; return dpdtAnalyze({ name:entryName, activate, url:request.getURL(), serDef, menuKey, activeMenuLookupKey, request, allowsInput, serviceDefRef, standardID, ID, - semantics, size, sRegion, + semantics, size, sRegion, dlData, prodTypeHint: prodTypeHint ?? 'unknown' }); } @@ -80,9 +79,11 @@ export function makeServiceDefDataProduct({ const CUTOUT_NAME_GUESS_LIST= ['size']; -function canMakeCutoutProduct(serDef, positionWP,sourceRow){ +function canMakeCutoutProduct(serDef,table,sourceRow,options){ + const key= options.dataProductsComponentKey ?? DEFAULT_DATA_PRODUCTS_COMPONENT_KEY; const {standardID,serDefParams} = serDef; + const positionWP= findCutoutTarget(key,serDef,table,sourceRow); if (!positionWP) { // look for ra/dec columns const wp= findWorldPtInServiceDef(serDef,sourceRow); if (!wp) return false; @@ -102,54 +103,23 @@ function canMakeCutoutProduct(serDef, positionWP,sourceRow){ return Boolean(nameGuess); } -function findWorldPtInServiceDef(serDef,sourceRow) { - const {serDefParams,sdSourceTable, dataLinkTableRowIdx} = serDef; - const raParam= serDefParams.find( ({UCD=''}) => - RA_UCDs.find( (testUcd) => UCD.toLowerCase().includes(testUcd)) ); - const decParam= serDefParams.find( ({UCD=''}) => - DEC_UCDs.find( (testUcd) => UCD.toLowerCase().includes(testUcd)) ); - if (!raParam && !decParam) return; - - let raVal= raParam.value; - let decVal= decParam.value; - - if (raVal && decVal) return makeWorldPt(raVal,decVal); - if (!sdSourceTable) return; - - const hasDLTable= isDatalinkTable(sdSourceTable); - const hasDLRow= isDefined(dataLinkTableRowIdx); - const hasSourceRow= isDefined(sourceRow); - const row= hasDLTable && hasDLRow ? dataLinkTableRowIdx : hasSourceRow ? sourceRow : undefined; - if (!raVal && raParam.ref) { - const col = getColumnByRef(sdSourceTable, raParam.ref); - if (col && row > -1) raVal = getCellValue(sdSourceTable, row, col.name); - } - - if (!decVal && decParam.ref) { - const col = getColumnByRef(sdSourceTable, decParam.ref); - if (col && row > -1) decVal = getCellValue(sdSourceTable, row, col.name); - } - - return (raVal && decVal) ? makeWorldPt(raVal,decVal) : undefined; -} - -function makeCutoutProduct({ name, serDef, sourceTable, sourceRow, idx, positionWP, activateParams, +function makeCutoutProduct({ name, serDef, sourceTable, sourceRow, idx, activateParams, dlData, options, titleStr, menuKey}) { const {accessURL, standardID, serDefParams, sdSourceTable} = serDef; const key= options.dataProductsComponentKey ?? DEFAULT_DATA_PRODUCTS_COMPONENT_KEY; - const cutoutSize= getComponentState(key,{})[SD_CUTOUT_KEY] ?? 0.0213; + const cutoutSize= getCutoutSize(key); + if (cutoutSize<=0) return; // must be greater than 0 - if (!positionWP) { - positionWP= findWorldPtInServiceDef(serDef,sourceRow); - } - if (!positionWP) return; // this must exist, should check in calling function + + const positionWP= findCutoutTarget(key,serDef,sourceTable,sourceRow); + if (!positionWP) return; // positionWP must exist let titleToUse= titleStr; if (isDefined(serDef.dataLinkTableRowIdx) && isObsCoreLike(sourceTable)) { // this service def, from datalink, in obscore (normal cawse) - titleToUse= getObsCoreRowMetaInfo(sourceTable,sourceRow)?.titleStr ?? titleStr; + titleToUse= getObsCoreRowMetaInfo(sourceTable,sourceRow)?.titleStr || name || titleStr; } let params; const cutoutOptions= {...options}; @@ -170,7 +140,7 @@ function makeCutoutProduct({ name, serDef, sourceTable, sourceRow, idx, position // note: the size is set as a number, if is a string it is coming from the dialog if (isNumber(cutoutSize) && cutoutSize===SD_DEFAULT_SPACIAL_CUTOUT_SIZE && sdSizeValue!==cutoutSize) { params= {[ucd] : sdSizeValue}; - dispatchComponentStateChange(key,{ [SD_CUTOUT_KEY]: sdSizeValue } ); + setCutoutSize(key,sdSizeValue); } else { params= {[ucd] : cutoutSize}; @@ -187,12 +157,12 @@ function makeCutoutProduct({ name, serDef, sourceTable, sourceRow, idx, position else { const valNum= parseInt(nameGuess.value) || SD_DEFAULT_PIXEL_CUTOUT_SIZE; params= {[nameGuess.name] : valNum}; - dispatchComponentStateChange(key,{ [SD_CUTOUT_KEY]: valNum+'px' } ); + setCutoutSize(key,valNum+'px'); } pixelBasedCutout= true; } } - const url= makeUrlFromParams(accessURL, serDef, idx, getComponentInputs(serDef,cutoutOptions,params)); + const url= makeUrlFromParams(accessURL, serDef, dlData?.rowIdx ?? idx, getComponentInputs(serDef,cutoutOptions,params)); const request = makeObsCoreRequest(url, positionWP, titleToUse, sourceTable, sourceRow); const tbl= sourceTable ?? sdSourceTable; @@ -200,8 +170,8 @@ function makeCutoutProduct({ name, serDef, sourceTable, sourceRow, idx, position tbl?.highlightedRow); return dpdtImage({ name:'Show: Cutout: ' + (titleToUse || name), - activate, menuKey, - extraction: createSingleImageExtraction(request), enableCutout:true, pixelBasedCutout, + activate, menuKey, dlData, + extraction: createSingleImageExtraction(request, dlData?.sourceObsCoreData), enableCutout:true, pixelBasedCutout, request, override:false, interpretedData:false, requestDefault:false}); } @@ -210,6 +180,7 @@ function makeCutoutProduct({ name, serDef, sourceTable, sourceRow, idx, position * return a list of inputs from the user that will go into the service descriptor URL * @param serDef * @param {DataProductsFactoryOptions} options + * @param moreParams * @return {Object.} */ function getComponentInputs(serDef, options, moreParams={}) { @@ -288,5 +259,5 @@ export function makeUrlFromParams(url, serDef, rowIdx, userInputParams = {}) { function logServiceDescriptor(baseUrl, sendParams, newUrl) { // console.log(`service descriptor base URL: ${baseUrl}`); // Object.entries(sendParams).forEach(([k,v]) => console.log(`param: ${k}, value: ${v}`)); - console.log(`service descriptor new URL: ${newUrl}`); + // console.log(`service descriptor new URL: ${newUrl}`); } diff --git a/src/firefly/js/metaConvert/vo/VORequest.js b/src/firefly/js/metaConvert/vo/VORequest.js index e3f0ba891..6a0121fdf 100644 --- a/src/firefly/js/metaConvert/vo/VORequest.js +++ b/src/firefly/js/metaConvert/vo/VORequest.js @@ -3,13 +3,14 @@ import {sprintf} from '../../externalSource/sprintf.js'; import {getCellValue, getColumn, getMetaEntry} from '../../tables/TableUtil.js'; import {PlotAttribute} from '../../visualize/PlotAttribute.js'; import RangeValues from '../../visualize/RangeValues.js'; +import {RequestType} from '../../visualize/RequestType'; import {TitleOptions, WebPlotRequest} from '../../visualize/WebPlotRequest.js'; import {ZoomType} from '../../visualize/ZoomType.js'; import {getSSATitle, isSSATable} from '../../voAnalyzer/TableAnalysis.js'; /** * - * @param dataSource + * @param dataSourc * @param positionWP * @param titleStr * @param {TableModel} table @@ -30,6 +31,8 @@ export function makeObsCoreRequest(dataSource, positionWP, titleStr, table, row) r.setTitleOptions(TitleOptions.FILE_NAME); } r.setPlotId(uniqueId('obscore-')); + r.setWorldPt(positionWP); + r.setRequestType(RequestType.URL); const emMinCol = getColumn(table, 'em_max', true); const emMaxCol = getColumn(table, 'em_max', true); diff --git a/src/firefly/js/tables/TableUtil.js b/src/firefly/js/tables/TableUtil.js index e655e4985..3ec6d87d5 100644 --- a/src/firefly/js/tables/TableUtil.js +++ b/src/firefly/js/tables/TableUtil.js @@ -1462,7 +1462,7 @@ function getTM(tableOrId) { * @return {String|undefined|*} value or the meta data or the defVal */ export function getMetaEntry(tableOrId,metaKey,defVal= undefined) { - const tableMeta= get(getTM(tableOrId),'tableMeta'); + const tableMeta= getTM(tableOrId)?.tableMeta; if (!tableMeta || !isString(metaKey)) return defVal; const keyUp = metaKey.toUpperCase(); const [foundKey,value]= Object.entries(tableMeta).find( ([k]) => k.toUpperCase()===keyUp) || []; @@ -1470,7 +1470,7 @@ export function getMetaEntry(tableOrId,metaKey,defVal= undefined) { } /** - * case insensitive search of meta data for boolean a entry, if not found return the defVal + * Case insensitive search of meta data for a boolean entry, if not found return the defVal * @param {TableModel|String} tableOrId - parameters accepts the table model or tha table id * @param {String} metaKey - the metadata key * @param {boolean} [defVal= false] - the defVal to return if not found, defaults to false @@ -1480,6 +1480,22 @@ export function getBooleanMetaEntry(tableOrId,metaKey,defVal= false) { return toBoolean(getMetaEntry(tableOrId,metaKey,undefined),Boolean(defVal),['true','t','yes','y']); } +/** + * Case insensitive search of meta data for an object entry, if not found return the defVal + * @param {TableModel|String} tableOrId - parameters accepts the table model or tha table id + * @param {String} metaKey - the metadata key + * @param {Object} [defVal= undefined] - the defVal to return if not found, defaults to undefined + * @return {Object} value or the meta data or the defVal + */ +export function getObjectMetaEntry(tableOrId,metaKey,defVal= undefined) { + try { + return JSON.parse(getMetaEntry(tableOrId,metaKey)); + } + catch { + return defVal; + } +} + /** * return true if this table contains any auxiliary data, like meta, links, and params * @param tbl_id diff --git a/src/firefly/js/templates/common/ttFeatureWatchers.js b/src/firefly/js/templates/common/ttFeatureWatchers.js index 281f4814f..c9d7e2398 100644 --- a/src/firefly/js/templates/common/ttFeatureWatchers.js +++ b/src/firefly/js/templates/common/ttFeatureWatchers.js @@ -5,7 +5,7 @@ import {dispatchAddTableTypeWatcherDef} from '../../core/MasterSaga.js'; import {dispatchTableUiUpdate, TABLE_LOADED} from '../../tables/TablesCntlr.js'; import {getActiveTableId, getMetaEntry, getTableUiByTblId, getTblById} from '../../tables/TableUtil.js'; import {DownloadButton, DownloadOptionPanel} from '../../ui/DownloadDialog.jsx'; -import {getTapObsCoreOptions, getTapObsCoreOptionsGuess} from '../../ui/tap/TableSearchHelpers.jsx'; +import {getTapObsCoreOptions, getTapObsCoreOptionsGuess} from '../../ui/tap/ObsCoreOptions'; import {hasObsCoreLikeDataProducts, isObsCoreLikeWithProducts} from '../../voAnalyzer/TableAnalysis.js'; import {getCatalogWatcherDef} from '../../visualize/saga/CatalogWatcher.js'; import {getUrlLinkWatcherDef} from '../../visualize/saga/UrlLinkWatcher.js'; diff --git a/src/firefly/js/ui/ActionsDropDownButton.jsx b/src/firefly/js/ui/ActionsDropDownButton.jsx index d924fa867..2979a88d0 100644 --- a/src/firefly/js/ui/ActionsDropDownButton.jsx +++ b/src/firefly/js/ui/ActionsDropDownButton.jsx @@ -62,9 +62,9 @@ function isSupported(sa,cenWpt,radius,cornerStr,table) { return sa.supported(sa, cenWpt, getValidSize(sa, radius), cornerStr); case SearchTypes.point: case SearchTypes.point_table_only: - return sa.supported(sa, cenWpt); + return sa.supported(table); case SearchTypes.wholeTable: - return sa.supported(sa, table); + return sa.supported(table); } } @@ -98,7 +98,7 @@ function SearchDropDown({searchActions, buttonRef, spacial, tbl_id}) { const makePartialList= (sActions, cenWpt, asCone) => { const buttons= sActions.map((sa,idx) => { - const text = getSearchTypeDesc(sa, cenWpt, radius, corners?.length); + const text = getSearchTypeDesc({sa, wp:cenWpt, size:radius, areaPtsLength:corners?.length, tbl_id}); let useSep = false; if (lastGroupId && sa.groupId !== lastGroupId && idx!==sActions.length-1) { lastGroupId = sa.groupId; @@ -107,7 +107,6 @@ function SearchDropDown({searchActions, buttonRef, spacial, tbl_id}) { if (!lastGroupId) lastGroupId = sa.groupId; return ( - {/*{useSep && }*/} - {doWholeTable && - Whole table actions - } + {doWholeTable && Whole table actions } { doWholeTable && wholeTableSearchActions.map((sa) => { - const text = getSearchTypeDesc(sa, cenWpt, radius, corners?.length); + const text = getSearchTypeDesc({sa, wp:cenWpt, size:radius, areaPtsLength:corners?.length, tbl_id}); + const tip= sa.tip ? `${sa.tip} for\n${text}` : text; return ( - doExecute(sa, cenWpt, radius, cornerStr, table)}/> + doExecute(sa, cenWpt, radius, cornerStr, table) }}/> ); })} { @@ -164,7 +160,6 @@ function SearchDropDown({searchActions, buttonRef, spacial, tbl_id}) { if (idx===1 && saOrder[0].length>0 && saList.length>0) { return ( - {/**/} {pList} ); diff --git a/src/firefly/js/ui/CutoutSizeDialog.jsx b/src/firefly/js/ui/CutoutSizeDialog.jsx index 07ae9ea6e..6d42f7c01 100644 --- a/src/firefly/js/ui/CutoutSizeDialog.jsx +++ b/src/firefly/js/ui/CutoutSizeDialog.jsx @@ -3,89 +3,220 @@ */ import {isString} from 'lodash'; import React, {useEffect} from 'react'; -import { - dispatchShowDialog, dispatchHideDialog, getComponentState, dispatchComponentStateChange -} from '../core/ComponentCntlr.js'; -import {SD_CUTOUT_KEY} from '../metaConvert/vo/ServDescProducts'; +import {Chip, Stack, Typography} from '@mui/joy'; +import {dispatchShowDialog, dispatchHideDialog} from '../core/ComponentCntlr.js'; +import {dispatchChangePointSelection, visRoot} from '../visualize/ImagePlotCntlr'; +import {PlotAttribute} from '../visualize/PlotAttribute'; +import {getActivePlotView, primePlot} from '../visualize/PlotViewUtil'; +import {FieldGroupCollapsible} from './panel/CollapsiblePanel'; +import { getCutoutSize, setAllCutoutParams, setPreferCutout } from './tap/ObsCoreOptions'; import {intValidator} from '../util/Validate'; import CompleteButton from './CompleteButton.jsx'; import DialogRootContainer from './DialogRootContainer.jsx'; -import {FieldGroup} from './FieldGroup'; +import {FieldGroup} from './FieldGroup.jsx'; import {PopupPanel} from './PopupPanel.jsx'; -import {useFieldGroupValue, useStoreConnector} from './SimpleComponent'; -import {SizeInputFields} from './SizeInputField'; +import {DEF_TARGET_PANEL_KEY, TargetPanel} from './TargetPanel'; +import {ValidationField} from './ValidationField.jsx'; +import {showYesNoPopup} from './PopupUtil.jsx'; +import {useFieldGroupValue, useStoreConnector} from './SimpleComponent.jsx'; +import {SizeInputFields} from './SizeInputField.jsx'; import HelpIcon from './HelpIcon.jsx'; -import {Stack} from '@mui/joy'; -import {ValidationField} from './ValidationField'; const DIALOG_ID= 'cutoutSizeDialog'; +const OVERRIDE_TARGET= 'OverrideTarget'; -export function showCutoutSizeDialog(cutoutDefSizeDeg, pixelBasedCutout, dataProductsComponentKey) { + +export function showCutoutSizeDialog({showingCutout, cutoutDefSizeDeg, pixelBasedCutout, tbl_id, cutoutCenterWP, + dataProductsComponentKey, enableCutoutFullSwitching, cutoutToFullWarning}) { const popup = ( - + - + ); DialogRootContainer.defineDialog(DIALOG_ID, popup); + showDialog(); +} + + +function showDialog() { + dispatchChangePointSelection('cutoutDialog', true); dispatchShowDialog(DIALOG_ID); } +function hideDialog() { + dispatchChangePointSelection('cutoutDialog', false); + dispatchHideDialog(DIALOG_ID); +} + + +function CutoutSizePanel({showingCutout,cutoutDefSizeDeg,pixelBasedCutout,dataProductsComponentKey, tbl_id, + cutoutCenterWP, enableCutoutFullSwitching, cutoutToFullWarning}) { + const cutoutFieldKey= dataProductsComponentKey+'-CutoutSize'; + let cutoutValue= useStoreConnector( () => getCutoutSize(dataProductsComponentKey)); + const [getCutoutFieldSize,setCutoutFieldSize]= useFieldGroupValue(cutoutFieldKey); + const [getOverrideTarget,setOverrideTarget]= useFieldGroupValue(OVERRIDE_TARGET); + const [getEnteredTarget,setEnteredTarget]= useFieldGroupValue(DEF_TARGET_PANEL_KEY); + + const activeWp= useStoreConnector(() => { + const plot= primePlot(visRoot()); + if (!plot) return; + const {pt}=plot.attributes[PlotAttribute.ACTIVE_POINT] ?? {}; + return pt; + }); + useEffect(() => { + if (activeWp) { + setOverrideTarget(activeWp); + setEnteredTarget(activeWp); + } + }, [activeWp]); -function CutoutSizePanel({cutoutDefSizeDeg,pixelBasedCutout,dataProductsComponentKey}) { - const cutoutFieldKey= dataProductsComponentKey+'-'+SD_CUTOUT_KEY; - let cutoutValue= useStoreConnector( () => getComponentState(dataProductsComponentKey)[SD_CUTOUT_KEY] ?? cutoutDefSizeDeg); - const [getCutoutSize,setCutoutSize]= useFieldGroupValue(cutoutFieldKey); if (isString(cutoutValue) && cutoutValue?.endsWith('px')) { cutoutValue= cutoutValue.substring(0,cutoutValue.length-2); } useEffect(() => { - setCutoutSize(cutoutValue); + setCutoutFieldSize(cutoutValue); },[cutoutFieldKey]); return ( - - { pixelBasedCutout ? - - : - - - } + + + { pixelBasedCutout ? + + : + + + } + + - updateCutout(r,cutoutFieldKey,dataProductsComponentKey,pixelBasedCutout)}/> + + updateCutout(r,cutoutFieldKey,dataProductsComponentKey,pixelBasedCutout,getOverrideTarget(),tbl_id)}/> + {showingCutout && enableCutoutFullSwitching && + + showFullImage(r,cutoutFieldKey,dataProductsComponentKey, + cutoutToFullWarning,tbl_id)}/> + } + ); } +function OptionalTarget({cutoutCenterWP}) { + + const [getEnteredTarget,setEnteredTarget]= useFieldGroupValue(DEF_TARGET_PANEL_KEY); + const [getOverrideTarget,setOverrideTarget]= useFieldGroupValue(OVERRIDE_TARGET); + const wpOverride= getOverrideTarget(); -function updateCutout(request,cutoutFieldKey,dataProductsComponentKey,pixelBasedCutout) { - dispatchComponentStateChange(dataProductsComponentKey, - { - [SD_CUTOUT_KEY]:`${request[cutoutFieldKey]}${pixelBasedCutout?'px':''}` - }); -} \ No newline at end of file + useEffect(() => { + const newWp= getEnteredTarget(); + if (newWp?.then) return; // ignore promises + if (!newWp || newWp?.toString()===cutoutCenterWP?.toString()) { + setOverrideTarget(undefined); + } + else { + setOverrideTarget(newWp); + } + }, [getEnteredTarget]); + + const header= ( + + + + Change Cutout Center: + + + Enter new point or click on image + + + + {`(${wpOverride?'Using Custom':'Using Default'})`} + + + + ); + + return ( + + + + { + setOverrideTarget(undefined); + setEnteredTarget(cutoutCenterWP); + }}> + reset to default + + + Warning: Changing the center could cause search to fail if new center is off of the tile + + + + ); +}; + + +function updateCutout(request,cutoutFieldKey,dataProductsComponentKey,pixelBasedCutout,overrideTarget,tbl_id) { + hideDialog(); + const size= `${request[cutoutFieldKey]}${pixelBasedCutout?'px':''}`; + setAllCutoutParams( dataProductsComponentKey, tbl_id, size, overrideTarget, true); + +} + +function showFullImage(request,cutoutFieldKey,dataProductsComponentKey,cutoutToFullWarning,tbl_id) { + if (cutoutToFullWarning) { + showYesNoPopup( + + {cutoutToFullWarning} + Show full image? + , + (id, yes) => { + if (yes) { + hideDialog(); + setPreferCutout(dataProductsComponentKey,tbl_id,false); + } + hideDialog(); + }, + 'Very large image', + {maxWidth:'50rem'} + ); + } + else { + hideDialog(); + setPreferCutout(dataProductsComponentKey,tbl_id,false); + } + +} diff --git a/src/firefly/js/ui/DefaultSearchActions.js b/src/firefly/js/ui/DefaultSearchActions.js index de6f2ecba..31a54694a 100644 --- a/src/firefly/js/ui/DefaultSearchActions.js +++ b/src/firefly/js/ui/DefaultSearchActions.js @@ -1,17 +1,21 @@ import {getMenu} from '../core/AppDataCntlr.js'; import {makeSearchActionObj, SearchTypes} from '../core/ClickToAction.js'; import {flux} from '../core/ReduxFlux.js'; +import {MetaConst} from '../data/MetaConst'; import {ServerParams} from '../data/ServerParams.js'; import {sprintf} from '../externalSource/sprintf.js'; -import {getTableUiByTblId, makeFileRequest} from '../api/ApiUtilTable.jsx'; +import {getActiveTableId, getMetaEntry, getTableUiByTblId, getTblById, makeFileRequest} from '../api/ApiUtilTable.jsx'; +import {extractDatalinkTable} from '../metaConvert/TableDataProductUtils'; import {makeVOCatalogRequest} from '../tables/TableRequestUtil.js'; import {dispatchTableSearch} from '../tables/TablesCntlr.js'; -import {findTableCenterColumns} from '../voAnalyzer/TableAnalysis.js'; +import { findTableCenterColumns, isFormatDataLink, isObsCoreLike } from '../voAnalyzer/TableAnalysis.js'; import {DEFAULT_FITS_VIEWER_ID} from '../visualize/MultiViewCntlr.js'; +import {getServiceDescriptors, isDataLinkServiceDesc} from '../voAnalyzer/VoDataLinkServDef'; import {setMultiSearchPanelTab} from './MultiSearchPanel.jsx'; import {Format} from 'firefly/data/FileAnalysis'; import {doJsonRequest} from 'firefly/core/JsonUtils'; import {showInfoPopup} from 'firefly/ui/PopupUtil'; +import {getObsCoreOption} from './tap/ObsCoreOptions'; //note - these two redundant function are here because of circular dependencies. // this file is imported very early and webpack is creating errors @@ -143,7 +147,6 @@ export const makeDefTableSearchActions= () => { execute: (sa, wp) => showImage( {searchParams: {wp, type: 'hipsImage'}}), searchDesc: 'Display HiPS for row' } ), - makeSearchActionObj({ cmd: 'tableTapUpload', groupId: 'tableTap', @@ -152,9 +155,21 @@ export const makeDefTableSearchActions= () => { searchType: SearchTypes.wholeTable, execute: (sa,table) => searchWholeTable(table), searchDesc: 'Use table as an upload to TAP search' - }) -]; - + }), + makeSearchActionObj({cmd:'showDatalinkTable', + groupId:'resolver', + label:'all data productions', + tip:'', + searchType: SearchTypes.point_table_only, + execute: () => showDatalinkTable(), + supported: (table) => canShowDatalinkTable(table), + searchDesc: ({tbl_id}) => { + const sourceId= getMetaEntry(tbl_id, MetaConst.OBSCORE_SOURCE_ID); + return getObsCoreOption( 'datalinkExtractTableDesc', sourceId, + 'Show table with all data products for this row (Datalink)'); + } + } ), + ]; }; export const makeExternalSearchActions = () => { @@ -188,7 +203,7 @@ export const makeExternalSearchActions = () => { min: .001, max: 5, execute: (sa,cenWpt,radius) => gotoAndSearchSimbad(cenWpt,radius), - searchDesc: (wp,size) => `Go to Simbad and search (cone) with radius of ${sprintf('%.4f',size)} degrees`} ), + searchDesc: ({wp,size}) => `Go to Simbad and search (cone) with radius of ${sprintf('%.4f',size)} degrees`} ), ]; }; @@ -237,6 +252,20 @@ function searchNed(cenWpt,radius) { dispatchTableSearch(request); } +function showDatalinkTable() { + const table= getTblById(getActiveTableId()); + if (!table) return; + const row= table.highlightedRow; + extractDatalinkTable(table,row,`Products (row ${row})`); +} + +function canShowDatalinkTable(table) { + const isObsCore= isObsCoreLike(table) && isFormatDataLink(table,table.highlightedRow); + if (isObsCore) return true; + const serDefAry= getServiceDescriptors(table); + return Boolean(serDefAry && isDataLinkServiceDesc(serDefAry[0])); +} + function searchSimbad(cenWpt,radius) { const base = 'http://simbad.cds.unistra.fr/simbad/sim-coo'; const params = { diff --git a/src/firefly/js/ui/PopupUtil.jsx b/src/firefly/js/ui/PopupUtil.jsx index 316ad783b..2368a9baa 100644 --- a/src/firefly/js/ui/PopupUtil.jsx +++ b/src/firefly/js/ui/PopupUtil.jsx @@ -111,20 +111,20 @@ function makeContent(content) { ); } -export function showYesNoPopup(content, clickSelection, title='Information') { +export function showYesNoPopup(content, clickSelection, title='Information', sx) { const results= ( - {makeYesNoContent(content, clickSelection)} + {makeYesNoContent(content, clickSelection, sx)} ); DialogRootContainer.defineDialog(INFO_POPUP, results); dispatchShowDialog(INFO_POPUP); } -function makeYesNoContent(content, clickSelection) { +function makeYesNoContent(content, clickSelection, sx) { return ( - + {content} diff --git a/src/firefly/js/ui/tap/ObsCore.jsx b/src/firefly/js/ui/tap/ObsCore.jsx index 4ab6a6c4b..4711eca2f 100644 --- a/src/firefly/js/ui/tap/ObsCore.jsx +++ b/src/firefly/js/ui/tap/ObsCore.jsx @@ -5,7 +5,7 @@ import {CheckboxGroupInputField} from 'firefly/ui/CheckboxGroupInputField'; import {ListBoxInputField} from 'firefly/ui/ListBoxInputField'; import { makeFieldErrorList, getPanelPrefix, LableSaptail, makePanelStatusUpdater, - Width_Column, DebugObsCore, makeCollapsibleCheckHeader, getTapObsCoreOptions, SpatialWidth + Width_Column, DebugObsCore, makeCollapsibleCheckHeader, SpatialWidth } from 'firefly/ui/tap/TableSearchHelpers'; import {tapHelpId} from 'firefly/ui/tap/TapUtil'; import {ValidationField} from 'firefly/ui/ValidationField'; @@ -17,6 +17,7 @@ import {useFieldGroupRerender, useFieldGroupValue, useFieldGroupWatch} from '../ import {ConstraintContext} from './Constraints.js'; import {AutoCompleteInput} from 'firefly/ui/AutoCompleteInput'; import {omit} from 'lodash'; +import {getTapObsCoreOptions} from './ObsCoreOptions'; const panelTitle = 'Observation Type and Source'; const panelValue = 'ObsCore'; diff --git a/src/firefly/js/ui/tap/ObsCoreOptions.js b/src/firefly/js/ui/tap/ObsCoreOptions.js new file mode 100644 index 000000000..7aa27e2a7 --- /dev/null +++ b/src/firefly/js/ui/tap/ObsCoreOptions.js @@ -0,0 +1,111 @@ +import {isObject, isString} from 'lodash'; +import {getAppOptions} from '../../core/AppDataCntlr'; +import {dispatchComponentStateChange, getComponentState} from '../../core/ComponentCntlr'; +import {MetaConst} from '../../data/MetaConst'; +import {getMetaEntry} from '../../tables/TableUtil'; +import {parseWorldPt} from '../../visualize/Point'; +import {getSearchTarget, makeWorldPtUsingCenterColumns} from '../../voAnalyzer/TableAnalysis'; +import {findWorldPtInServiceDef} from '../../voAnalyzer/VoDataLinkServDef'; + +const PREFER_CUTOUT_KEY = 'preferCutout'; +const SD_CUTOUT_SIZE_KEY = 'sdCutoutSize'; +const SD_CUTOUT_WP_OVERRIDE = 'sdCutoutWpOverride'; +const SD_DEFAULT_SPACIAL_CUTOUT_SIZE = .01; +const SD_DEFAULT_PIXEL_CUTOUT_SIZE = 200; + + + +export const getTapObsCoreOptions = (obsCoreSource) => + getAppOptions().tapObsCore?.[obsCoreSource] ?? getAppOptions().tapObsCore ?? {}; + +/** + * @param key + * @param [obsCoreSource] + * @param [defVal] + * @return {*} + */ +export function getObsCoreOption(key, obsCoreSource = undefined, defVal) { + const slOps = obsCoreSource ? getAppOptions().tapObsCore?.[obsCoreSource] ?? {} : {}; + const ops = getAppOptions().tapObsCore ?? {}; + return slOps[key] ?? ops[key] ?? defVal; +} + +export function getTapObsCoreOptionsGuess(serviceLabelGuess) { + const {tapObsCore = {}} = getAppOptions(); + if (!serviceLabelGuess) return tapObsCore; + const guessKey = Object.entries(tapObsCore) + .find(([key, value]) => isObject(value) && serviceLabelGuess.includes(key))?.[0]; + return getTapObsCoreOptions(guessKey); +} + +/** + * + * @param {String} dataProductsComponentKey + * @param {String} tbl_id + * @return {boolean} + */ +export function getPreferCutout(dataProductsComponentKey, tbl_id) { + const obsCoreSource = getMetaEntry(tbl_id, MetaConst.OBSCORE_SOURCE_ID); + const DEF_PREFER_CUTOUT = getObsCoreOption(PREFER_CUTOUT_KEY, obsCoreSource, true); + const result = getComponentState(dataProductsComponentKey)[PREFER_CUTOUT_KEY]; + if (!result) return DEF_PREFER_CUTOUT; + if (!isObject(result)) return DEF_PREFER_CUTOUT; + return result[tbl_id] ?? result.LAST_PREF ?? DEF_PREFER_CUTOUT; +} + +/** + * + * @param {String} dataProductsComponentKey + * @param {String} tbl_id + * @param {boolean} preferCutout + */ +export function setPreferCutout(dataProductsComponentKey, tbl_id, preferCutout) { + const result = getComponentState(dataProductsComponentKey)[PREFER_CUTOUT_KEY] ?? {}; + const newState = {...result, [tbl_id]: preferCutout, LAST_PREF: preferCutout}; + dispatchComponentStateChange(dataProductsComponentKey, {[PREFER_CUTOUT_KEY]: newState}); +} + +export function getCutoutSize(dataProductsComponentKey, tbl_id) { + const obsCoreSource = getMetaEntry(tbl_id, MetaConst.OBSCORE_SOURCE_ID); + return getComponentState(dataProductsComponentKey, {})[SD_CUTOUT_SIZE_KEY] ?? + getObsCoreOption('cutoutDefSizeDeg', obsCoreSource, SD_DEFAULT_SPACIAL_CUTOUT_SIZE); +} + +export const setCutoutSize= (dataProductsComponentKey, cutoutSize) => + dispatchComponentStateChange(dataProductsComponentKey, { [SD_CUTOUT_SIZE_KEY]: cutoutSize}); + +/** + * equivalent to setPreferCutout, setCutoutSize, setCutoutTargetOverride + * @param dataProductsComponentKey + * @param tbl_id + * @param cutoutSize + * @param overrideTarget + * @param preferCutout + */ +export function setAllCutoutParams(dataProductsComponentKey, tbl_id, cutoutSize, overrideTarget = undefined, preferCutout) { + const result = getComponentState(dataProductsComponentKey)[PREFER_CUTOUT_KEY] ?? {}; + const newPreferState = {...result, [tbl_id]: preferCutout, LAST_PREF: preferCutout}; + let wp= overrideTarget; + if (overrideTarget && isString(overrideTarget)) { + wp= parseWorldPt(overrideTarget); + } + dispatchComponentStateChange(dataProductsComponentKey, + { + [PREFER_CUTOUT_KEY]: newPreferState, + [SD_CUTOUT_SIZE_KEY]: cutoutSize, + [SD_CUTOUT_WP_OVERRIDE]: wp, + }); +} + +export const setCutoutTargetOverride= (dataProductsComponentKey, wp)=> + dispatchComponentStateChange(dataProductsComponentKey, {[SD_CUTOUT_WP_OVERRIDE]: wp}); + +export const getCutoutTargetOverride= (dataProductsComponentKey) => + getComponentState(dataProductsComponentKey, {})[SD_CUTOUT_WP_OVERRIDE]; + +export function findCutoutTarget(dataProductsComponentKey, serDef, table, row) { + let positionWP = getSearchTarget(table?.request, table) ?? makeWorldPtUsingCenterColumns(table, row); + if (!positionWP) positionWP= serDef?.positionWP; + if (getCutoutTargetOverride(dataProductsComponentKey)) return getCutoutTargetOverride(dataProductsComponentKey); + return positionWP ?? findWorldPtInServiceDef(serDef, row); +} \ No newline at end of file diff --git a/src/firefly/js/ui/tap/SIASearchRootPanel.jsx b/src/firefly/js/ui/tap/SIASearchRootPanel.jsx index 4b96f9a18..c14c5f567 100644 --- a/src/firefly/js/ui/tap/SIASearchRootPanel.jsx +++ b/src/firefly/js/ui/tap/SIASearchRootPanel.jsx @@ -424,8 +424,7 @@ function onSIAv2SearchSubmit(request, serviceUrl, siaMeta, siaState, showErrors= const cStr= constraints.join('&'); const hasMaxrec = !isNaN(parseInt(request.maxrec)); const maxrec = parseInt(request.maxrec); - var baseRequestUrl= `${serviceUrl}?${cStr}`; - + const baseRequestUrl= `${serviceUrl}?${cStr}`; const doSubmit = () => { @@ -435,7 +434,11 @@ function onSIAv2SearchSubmit(request, serviceUrl, siaMeta, siaState, showErrors= if (hips) additionalSiaMeta[MetaConst.COVERAGE_HIPS]= hips; const title= makeNumberedTitle(userTitle || 'SIA Search'); const treq= makeFileRequest(title,new URL(url).toString()); - treq.META_INFO= {...treq.META_INFO, ...additionalSiaMeta}; + treq.META_INFO= { + ...treq.META_INFO, + ...additionalSiaMeta, + [MetaConst.OBSCORE_SOURCE_ID] : getSiaServiceLabel(serviceUrl), + }; console.log('sia search: ' + url, new URL(url).toString()); dispatchTableSearch(treq, {backgroundable: true, showFilters: true, showInfoButton: true}); }; diff --git a/src/firefly/js/ui/tap/SiaUtil.js b/src/firefly/js/ui/tap/SiaUtil.js index 5246bb1f3..93c23c531 100644 --- a/src/firefly/js/ui/tap/SiaUtil.js +++ b/src/firefly/js/ui/tap/SiaUtil.js @@ -34,7 +34,6 @@ function getMetaFromTable(table) { return inputParams; } - export async function loadSiaV2Meta(serviceUrl) { if (serviceMetaCache[serviceUrl]) return serviceMetaCache[serviceUrl]; serviceMetaCache[serviceUrl]= await doLoadSiaV2Meta(serviceUrl); diff --git a/src/firefly/js/ui/tap/TableSearchHelpers.jsx b/src/firefly/js/ui/tap/TableSearchHelpers.jsx index 3b8007451..f85694585 100644 --- a/src/firefly/js/ui/tap/TableSearchHelpers.jsx +++ b/src/firefly/js/ui/tap/TableSearchHelpers.jsx @@ -1,11 +1,11 @@ +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import KeyboardDoubleArrowDown from '@mui/icons-material/KeyboardDoubleArrowDown'; import KeyboardDoubleArrowUp from '@mui/icons-material/KeyboardDoubleArrowUp'; -import ReceiptLongOutlinedIcon from '@mui/icons-material/ReceiptLongOutlined'; -import {Box, Button, IconButton, Stack, Typography} from '@mui/joy'; +import {Box, IconButton, Stack, Typography} from '@mui/joy'; import HelpIcon from 'firefly/ui/HelpIcon'; import {SwitchInputFieldView} from 'firefly/ui/SwitchInputField'; -import {isEqual, isObject} from 'lodash'; +import {isEqual} from 'lodash'; import Prism from 'prismjs'; import PropTypes from 'prop-types'; import React, {useEffect, useRef} from 'react'; @@ -16,7 +16,6 @@ import {RadioGroupInputFieldView} from '../RadioGroupInputFieldView.jsx'; import {useFieldGroupValue} from '../SimpleComponent.jsx'; import {showResultTitleDialog} from './ResultTitleDialog'; import {ADQL_QUERY_KEY, makeTapSearchTitle, USER_ENTERED_TITLE} from './TapUtil'; -import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; export const HeaderFont = {fontSize: 12, fontWeight: 'bold', alignItems: 'center'}; @@ -33,30 +32,6 @@ export const SpatialPanelWidth = Math.max(Width_Time_Wrapper * 2, SpatialWidth) const DEF_ERR_MSG= 'Constraints Error'; -export const getTapObsCoreOptions= (serviceLabel) => - getAppOptions().tapObsCore?.[serviceLabel] ?? getAppOptions().tapObsCore ?? {}; - -/** - * @param key - * @param [serviceLabel] - * @return {*} - */ -export function getObsCoreOption(key,serviceLabel=undefined) { - const slOps= serviceLabel ? getAppOptions().tapObsCore?.[serviceLabel] ?? {} : {}; - const ops= getAppOptions().tapObsCore ?? {}; - return slOps[key] ?? ops[key]; -} - - -export function getTapObsCoreOptionsGuess(serviceLabelGuess) { - const {tapObsCore={}}= getAppOptions(); - if (!serviceLabelGuess) return tapObsCore; - const guessKey= Object.entries(tapObsCore) - .find( ([key,value]) => isObject(value) && serviceLabelGuess.includes(key))?.[0]; - return getTapObsCoreOptions(guessKey); -} - - /** * make a FieldErrorList object * @returns {FieldErrorList} @@ -101,6 +76,7 @@ export function makePanelStatusUpdater(panelActive,panelTitle,defErrorMessage) { * @param {InputConstraints} constraints * @param {ConstraintResult} lastConstraintResult * @param {Function} setConstraintResult - a function to set the constraint result setConstraintResult(ConstraintResult) + * @param {boolean} useSIAv2 * @String string - panel message */ return (constraints, lastConstraintResult, setConstraintResult, useSIAv2= false) => { diff --git a/src/firefly/js/ui/tap/TapSearchSubmit.js b/src/firefly/js/ui/tap/TapSearchSubmit.js index c201a09d8..a3d730fe0 100644 --- a/src/firefly/js/ui/tap/TapSearchSubmit.js +++ b/src/firefly/js/ui/tap/TapSearchSubmit.js @@ -77,7 +77,12 @@ export function onTapSearchSubmit(request, serviceUrl, tapBrowserState, addition additionalTapMeta.serviceLabel= serviceLabel; if (hips) additionalTapMeta[MetaConst.COVERAGE_HIPS]= hips; - treq.META_INFO= {...treq.META_INFO, ...additionalTapMeta, ...metaInfo }; + treq.META_INFO= { + ...treq.META_INFO, + ...additionalTapMeta, + ...metaInfo, + [MetaConst.OBSCORE_SOURCE_ID] : serviceLabel, + }; dispatchTableSearch(treq, {backgroundable: true, showFilters: true, showInfoButton: true}); }; diff --git a/src/firefly/js/ui/tap/TapViewType.jsx b/src/firefly/js/ui/tap/TapViewType.jsx index 77c2ee853..1b3ee5b78 100644 --- a/src/firefly/js/ui/tap/TapViewType.jsx +++ b/src/firefly/js/ui/tap/TapViewType.jsx @@ -11,11 +11,12 @@ import {ListBoxInputFieldView} from '../ListBoxInputField.jsx'; import {SplitContent} from '../panel/DockLayoutPanel'; import {useFieldGroupMetaState} from '../SimpleComponent.jsx'; import {AdvancedADQL} from './AdvancedADQL.jsx'; +import {getTapObsCoreOptions} from './ObsCoreOptions'; import {showTableSelectPopup} from './TableChooser.jsx'; import {TableColumnsConstraints, TableColumnsConstraintsToolbar} from './TableColumnsConstraints.jsx'; import { - ADQL, SINGLE, SpatialPanelWidth, NavButtons, TableTypeButton, getTapObsCoreOptions + ADQL, SINGLE, SpatialPanelWidth, NavButtons, TableTypeButton } from './TableSearchHelpers.jsx'; import {TableSearchMethods} from './TableSearchMethods.jsx'; import { diff --git a/src/firefly/js/ui/tap/WavelengthPanel.jsx b/src/firefly/js/ui/tap/WavelengthPanel.jsx index edf5524f8..9c525d3f9 100644 --- a/src/firefly/js/ui/tap/WavelengthPanel.jsx +++ b/src/firefly/js/ui/tap/WavelengthPanel.jsx @@ -9,8 +9,9 @@ import {RadioGroupInputField} from '../RadioGroupInputField.jsx'; import {useFieldGroupRerender, useFieldGroupWatch} from '../SimpleComponent.jsx'; import {ValidationField} from '../ValidationField.jsx'; import {makeAdqlQueryRangeFragment, ConstraintContext, siaQueryRange} from './Constraints.js'; +import {getTapObsCoreOptions} from './ObsCoreOptions'; import { - DebugObsCore, getPanelPrefix, getTapObsCoreOptions, LeftInSearch, makeCollapsibleCheckHeader, + DebugObsCore, getPanelPrefix, LeftInSearch, makeCollapsibleCheckHeader, makeFieldErrorList, makePanelStatusUpdater, SmallFloatNumericWidth, SpatialWidth, } from './TableSearchHelpers.jsx'; diff --git a/src/firefly/js/visualize/HiPSMocUtil.js b/src/firefly/js/visualize/HiPSMocUtil.js index 5badee050..34b884622 100644 --- a/src/firefly/js/visualize/HiPSMocUtil.js +++ b/src/firefly/js/visualize/HiPSMocUtil.js @@ -185,10 +185,11 @@ export function getMocOrderIndex(Nuniq) { * @param {string} params.uniqColName column name for uniq number * @param {boolean} params.tablePreloaded - true if the MOC table has already been loaded * @param {string} [params.color] - color string + * @param {string} [params.maxFetchDepth] - maximum healpix order to fetch * @param {string} [params.mocGroupDefColorId ] - group color id * @returns {T|SelectInfo|*|{}} */ -export function addNewMocLayer({tbl_id, title, fitsPath, mocUrl, uniqColName = 'NUNIQ', +export function addNewMocLayer({tbl_id, title, fitsPath, mocUrl, uniqColName = 'NUNIQ', maxFetchDepth, tablePreloaded=false, color, mocGroupDefColorId }) { const dls = getDrawLayersByType(getDlAry(), HiPSMOC.TYPE_ID); let dl = dls.find((oneLayer) => oneLayer.drawLayerId === tbl_id); @@ -198,7 +199,7 @@ export function addNewMocLayer({tbl_id, title, fitsPath, mocUrl, uniqColName = ' if (title) title= 'MOC - ' + title; const mocFitsInfo = {fitsPath, mocUrl, uniqColName, tbl_id, tablePreloaded}; dl = dispatchCreateDrawLayer(HiPSMOC.TYPE_ID, - {mocFitsInfo,title,layersPanelLayoutId:'mocUIGroup', color, mocGroupDefColorId }); + {mocFitsInfo,title,layersPanelLayoutId:'mocUIGroup', color, mocGroupDefColorId,maxFetchDepth}); } return dl; } diff --git a/src/firefly/js/visualize/ImViewFilterDisplay.js b/src/firefly/js/visualize/ImViewFilterDisplay.js index 557af1674..80e9fbf98 100644 --- a/src/firefly/js/visualize/ImViewFilterDisplay.js +++ b/src/firefly/js/visualize/ImViewFilterDisplay.js @@ -19,7 +19,16 @@ export const IMAGE_VIEW_TABLE_ID = 'active-image-view-list-table'; export const EXPANDED_OPTIONS_DIALOG_ID= 'ExpandedOptionsPopup'; export const HIDDEN='_HIDDEN'; -const [NAME_IDX, WAVE_LENGTH_UM, PID_IDX, STATUS, PROJ_TYPE_DESC, WAVE_TYPE, DATA_HELP_URL, ROW_IDX] = [0, 1, 2, 3, 4, 5, 6, 7]; +const [NAME_IDX, WAVE_LENGTH_UM, PID_IDX, STATUS, PROJ_TYPE_DESC, WAVE_TYPE, DATA_HELP_URL, + OBS_TITLE_IDX, S_RA_IDX, S_DEC_IDX, + T_MIN_IDX, T_MAX_IDX, EM_MIN_IDX, EM_MAX_IDX, CALIB_LEVEL_IDX, DATAPRODUCT_TYPE_IDX, DATAPRODUCT_SUBTYPE_IDX, + FACILITY_NAME_IDX, INSTRUMENT_NAME_IDX, + OBS_COLLECTION_IDX, S_REGION_IDX, ROW_IDX] = [...Array(22).keys()]; + + + + +const wlUnits= getFormattedWaveLengthUnits('um'); const columnsTemplate = []; columnsTemplate[NAME_IDX] = {name: 'Name', type: 'char', width: 22}; @@ -27,12 +36,7 @@ columnsTemplate[PID_IDX] = {name: 'plotId', type: 'char', width: 10, visibility: columnsTemplate[STATUS] = {name: 'Status', type: 'char', width: 10}; columnsTemplate[PROJ_TYPE_DESC] = {name: 'Type', type: 'char', width: 8}; columnsTemplate[WAVE_TYPE] = {name: 'Band', type: 'char', width: 9}; -columnsTemplate[WAVE_LENGTH_UM] = { - name: 'Wavelength', - type: 'double', - width: 8, - units: getFormattedWaveLengthUnits('um') -}; +columnsTemplate[WAVE_LENGTH_UM] = { name: 'Wavelength', type: 'double', width: 8, units: wlUnits }; columnsTemplate[DATA_HELP_URL] = { name: 'Help', @@ -42,6 +46,34 @@ columnsTemplate[DATA_HELP_URL] = { }; +columnsTemplate[OBS_TITLE_IDX] = {name: 'obs_title', type: 'show', width: 20}; +columnsTemplate[S_RA_IDX] = {name: 's_ra', type: 'double', width: 9}; +columnsTemplate[S_DEC_IDX] = {name: 's_dec', type: 'double', width: 9}; +columnsTemplate[EM_MIN_IDX] = {name: 'em_min', type: 'double', width: 8, units: wlUnits}; +columnsTemplate[EM_MAX_IDX] = {name: 'em_max', type: 'double', width: 8, units: wlUnits}; +columnsTemplate[T_MAX_IDX] = {name: 't_max', type: 'double', width: 9}; +columnsTemplate[T_MIN_IDX] = {name: 't_min', type: 'double', width: 9}; +columnsTemplate[T_MAX_IDX] = {name: 't_max', type: 'double', width: 9}; +columnsTemplate[CALIB_LEVEL_IDX] = {name: 'calib_level', type: 'show', width: 6}; +columnsTemplate[DATAPRODUCT_TYPE_IDX] = {name: 'dataproduct_type', type: 'show', width: 20}; +columnsTemplate[DATAPRODUCT_SUBTYPE_IDX] = {name: 'dataproduct_subtype', type: 'show', width: 20}; +columnsTemplate[OBS_COLLECTION_IDX] = {name: 'obs_collection', type: 'show', width: 20}; +columnsTemplate[FACILITY_NAME_IDX] = {name: 'facility_name', type: 'show', width: 20}; +columnsTemplate[INSTRUMENT_NAME_IDX] = {name: 'instrument_name', type: 'show', width: 20}; +columnsTemplate[S_REGION_IDX] = {name: 's_region', type: 'show', width: 10}; + + + + + + + + + + + + + const getAttribute = (attributes, attribute, def='') => attributes?.[attribute] ?? def; const makeEnumValues = (data, idx) => uniq(data.map((d) => d[idx]).filter((d) => d)).join(','); @@ -90,6 +122,25 @@ export function makeImViewDisplayModel(tbl_id, plotViewAry, allIds, oldModel) { row[WAVE_TYPE] = getAttribute(attributes, PlotAttribute.WAVE_TYPE); row[WAVE_LENGTH_UM] = parseFloat(getAttribute(attributes, PlotAttribute.WAVE_LENGTH_UM, 0.0)); row[DATA_HELP_URL] = getAttribute(attributes, PlotAttribute.DATA_HELP_URL); + + const {sourceObsCoreData}= plot.attributes; + if (sourceObsCoreData) { + row[S_RA_IDX]= sourceObsCoreData.s_ra; + row[S_DEC_IDX]= sourceObsCoreData.s_dec; + row[T_MAX_IDX]= sourceObsCoreData.t_max; + row[T_MIN_IDX]= sourceObsCoreData.t_min; + row[T_MAX_IDX]= sourceObsCoreData.t_max; + row[EM_MIN_IDX]= sourceObsCoreData.em_min; + row[EM_MAX_IDX]= sourceObsCoreData.em_max; + row[CALIB_LEVEL_IDX]= sourceObsCoreData.calib_level; + row[DATAPRODUCT_TYPE_IDX]= sourceObsCoreData.dataproduct_type; + row[DATAPRODUCT_SUBTYPE_IDX]= sourceObsCoreData.dataproduct_subtype; + row[OBS_COLLECTION_IDX]= sourceObsCoreData.obs_collection; + row[FACILITY_NAME_IDX]= sourceObsCoreData.facility_name; + row[OBS_TITLE_IDX]= sourceObsCoreData.obs_title; + row[INSTRUMENT_NAME_IDX]= sourceObsCoreData.instrument_name; + row[S_REGION_IDX]= sourceObsCoreData.s_region; + } return row; }); diff --git a/src/firefly/js/visualize/SearchRefinementTool.jsx b/src/firefly/js/visualize/SearchRefinementTool.jsx index 462cf89d3..c18c2e98a 100644 --- a/src/firefly/js/visualize/SearchRefinementTool.jsx +++ b/src/firefly/js/visualize/SearchRefinementTool.jsx @@ -210,7 +210,7 @@ function SearchDropDown({searchActions, cenWpt, size, polyStr, whichOverlay}) { .filter( (sa) => searchMatches(sa, whichOverlay===CONE_CHOICE_KEY, whichOverlay===POLY_CHOICE_KEY, whichOverlay===CONE_CHOICE_KEY) ) .map( (sa) => { - const text= getSearchTypeDesc(sa,cenWpt,Number(size),polyStrLen); + const text= getSearchTypeDesc({sa,wp:cenWpt,size:Number(size),areaPtsLength:polyStrLen }); return ( { plot.attributes= {...plot.attributes, ...getNewAttributes(plot)}; - plot.plotImageId= `${pv.plotId}--${pv.plotViewCtx.plotCounter}`; + plot.plotImageId= `${pv.plotId}--${pv.plotViewCtx.plotCounter}--${getNextStaticPlotCount()}`; pv.plotViewCtx.plotCounter++; }); diff --git a/src/firefly/js/visualize/task/PlotAdminTask.js b/src/firefly/js/visualize/task/PlotAdminTask.js index 033f2183a..919c75ca8 100644 --- a/src/firefly/js/visualize/task/PlotAdminTask.js +++ b/src/firefly/js/visualize/task/PlotAdminTask.js @@ -9,8 +9,10 @@ import ImagePlotCntlr, {dispatchChangeActivePlotView, dispatchPlotHiPS, import {getExpandedViewerItemIds, findViewerWithItemId, getMultiViewRoot, IMAGE} from '../MultiViewCntlr.js'; import PointSelection from '../../drawingLayers/PointSelection.js'; -import {dispatchAttachLayerToPlot, dispatchCreateDrawLayer, - dispatchDetachLayerFromPlot, DRAWING_LAYER_KEY} from '../DrawLayerCntlr.js'; +import { + dispatchAttachLayerToPlot, dispatchCreateDrawLayer, dispatchDestroyDrawLayer, + dispatchDetachLayerFromPlot, DRAWING_LAYER_KEY +} from '../DrawLayerCntlr.js'; import { getPlotViewById, applyToOnePvOrAll, isDrawLayerAttached, primePlot, getDrawLayerByType, removeRawDataByPlotView } from '../PlotViewUtil.js'; import {isImage} from '../WebPlot.js'; @@ -84,6 +86,7 @@ export function changePointSelectionActionCreator(rawAction) { } else if (!enabled) { detachAll(plotViewAry,dl); + if (dl) dispatchDestroyDrawLayer(dl.drawLayerId); } }; } diff --git a/src/firefly/js/visualize/task/PlotHipsTask.js b/src/firefly/js/visualize/task/PlotHipsTask.js index 55c2d8bbc..08ea4bc69 100644 --- a/src/firefly/js/visualize/task/PlotHipsTask.js +++ b/src/firefly/js/visualize/task/PlotHipsTask.js @@ -284,11 +284,12 @@ async function makeHiPSPlot(rawAction, dispatcher) { export function createHiPSMocLayerFromPreloadedTable({tbl_id,title, fitsPath, mocUrl, plotId, visible=false, - color, mocGroupDefColorId, attachAllPlot=false} ) { + maxFetchDepth, color, mocGroupDefColorId, attachAllPlot=false} ) { const table= getTblById(tbl_id); if (!table) return; const uniqColName= table.tableData.columns[0].name; - const dl = addNewMocLayer({ tbl_id, title, fitsPath, mocUrl, uniqColName, color, tablePreloaded:true, mocGroupDefColorId }); + const dl = addNewMocLayer({ tbl_id, title, fitsPath, mocUrl, uniqColName, + color, tablePreloaded:true, maxFetchDepth, mocGroupDefColorId }); if (dl && plotId) { dispatchAttachLayerToPlot(dl.drawLayerId, plotId, attachAllPlot, visible, true); } diff --git a/src/firefly/js/visualize/ui/ImageMetaDataToolbar.jsx b/src/firefly/js/visualize/ui/ImageMetaDataToolbar.jsx index 54f152628..c84ad9ebb 100644 --- a/src/firefly/js/visualize/ui/ImageMetaDataToolbar.jsx +++ b/src/firefly/js/visualize/ui/ImageMetaDataToolbar.jsx @@ -49,10 +49,14 @@ export class ImageMetaDataToolbar extends Component { render() { const {activeTable}= this.state; - const {visRoot, viewerId, viewerPlotIds, layoutType, dlAry, makeDropDown, serDef, factoryKey, enableCutout, pixelBasedCutout= false}= this.props; + const {visRoot, viewerId, viewerPlotIds, layoutType, dlAry, makeDropDown, serDef, factoryKey, enableCutout, + cutoutToFullWarning, + enableCutoutFullSwitching= false, pixelBasedCutout= false}= this.props; return ( @@ -70,6 +74,8 @@ ImageMetaDataToolbar.propTypes= { factoryKey: PropTypes.string, makeDropDown: PropTypes.func, enableCutout: PropTypes.bool, + enableCutoutFullSwitching: PropTypes.bool, pixelBasedCutout: PropTypes.bool, + cutoutToFullWarning: PropTypes.string, serDef: PropTypes.object }; diff --git a/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx b/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx index beb5ad272..b7a945739 100644 --- a/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx +++ b/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx @@ -5,13 +5,11 @@ import {Sheet, Stack} from '@mui/joy'; import {isEmpty, isEqual, omit} from 'lodash'; import React from 'react'; -import PropTypes from 'prop-types'; -import {SD_CUTOUT_KEY} from '../../metaConvert/vo/ServDescProducts'; +import PropTypes, {arrayOf, bool, func, string, object} from 'prop-types'; import {getTblInfo} from '../../tables/TableUtil.js'; -import {getComponentState} from '../../core/ComponentCntlr.js'; import {showCutoutSizeDialog} from '../../ui/CutoutSizeDialog.jsx'; import {useStoreConnector} from '../../ui/SimpleComponent.jsx'; -import {getObsCoreOption} from '../../ui/tap/TableSearchHelpers'; +import {findCutoutTarget, getCutoutSize, getObsCoreOption, getPreferCutout} from '../../ui/tap/ObsCoreOptions'; import {makeFoVString} from '../ZoomUtil.js'; import {ToolbarButton, ToolbarHorizontalSeparator} from '../../ui/ToolbarButton.jsx'; import {showInfoPopup} from '../../ui/PopupUtil.jsx'; @@ -28,12 +26,16 @@ import ContentCutRoundedIcon from '@mui/icons-material/ContentCutRounded'; export function ImageMetaDataToolbarView({viewerId, viewerPlotIds=[], layoutType, factoryKey, serDef, - enableCutout, pixelBasedCutout, + enableCutout, pixelBasedCutout,enableCutoutFullSwitching, + cutoutToFullWarning, activeTable, makeDataProductsConverter, makeDropDown}) { const converter= makeDataProductsConverter(activeTable,factoryKey) || {}; const {canGrid, hasRelatedBands, converterId, maxPlots, threeColor, dataProductsComponentKey}= converter ?? {}; - const cutoutValue= useStoreConnector( () => getComponentState(dataProductsComponentKey)[SD_CUTOUT_KEY]) ?? getObsCoreOption('cutoutDefSizeDeg') ?? .01; + const cutoutValue= useStoreConnector( () => getCutoutSize(dataProductsComponentKey)); + const preferCutout= useStoreConnector( () => getPreferCutout(dataProductsComponentKey,activeTable?.tbl_id)); + + const cutoutCenterWP= findCutoutTarget(dataProductsComponentKey,serDef,activeTable,activeTable?.highlightedRow); if (!converter) return
; let cSize=''; @@ -102,8 +104,22 @@ export function ImageMetaDataToolbarView({viewerId, viewerPlotIds=[], layoutType {makeDropDown ? makeDropDown() : false} {enableCutout && } - text={`${cSize}`} onClick={() => showCutoutSizeDialog(cutoutValue,pixelBasedCutout,dataProductsComponentKey)}/> + icon={} text={`${cSize}`} + title='Showing cutout of image, click here to modify cutout or show original image (with all extensions)' + onClick={() => + showCutoutSizeDialog({ + showingCutout:true,cutoutDefSizeDeg:cutoutValue,pixelBasedCutout, + tbl_id:activeTable?.tbl_id, cutoutCenterWP, + dataProductsComponentKey,enableCutoutFullSwitching, cutoutToFullWarning})}/> + } + {!enableCutout && enableCutoutFullSwitching && + } text={'Off'} + title='Showing original image (with all extensions), click here to show a cutout' + onClick={() => + showCutoutSizeDialog({showingCutout:false,cutoutDefSizeDeg:cutoutValue,pixelBasedCutout, + tbl_id:activeTable?.tbl_id, cutoutCenterWP, + dataProductsComponentKey,enableCutoutFullSwitching})}/> } {metaControls && @@ -122,18 +138,20 @@ export function ImageMetaDataToolbarView({viewerId, viewerPlotIds=[], layoutType } ImageMetaDataToolbarView.propTypes= { - dlAry : PropTypes.arrayOf(PropTypes.object), - activePlotId : PropTypes.string, - viewerId : PropTypes.string.isRequired, - layoutType : PropTypes.string.isRequired, - viewerPlotIds : PropTypes.arrayOf(PropTypes.string).isRequired, - activeTable: PropTypes.object, - makeDataProductsConverter: PropTypes.func, - makeDropDown: PropTypes.func, - serDef: PropTypes.object, - enableCutout: PropTypes.bool, - pixelBasedCutout: PropTypes.bool, - factoryKey: PropTypes.string + dlAry : arrayOf(object), + activePlotId : string, + viewerId : string.isRequired, + layoutType : string.isRequired, + viewerPlotIds : arrayOf(PropTypes.string).isRequired, + activeTable: object, + makeDataProductsConverter: func, + makeDropDown: func, + serDef: object, + enableCutout: bool, + pixelBasedCutout: bool, + factoryKey: string, + cutoutToFullWarning: string, + enableCutoutFullSwitching: bool }; diff --git a/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx b/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx index 9b1fd7b29..1ca17910e 100644 --- a/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx +++ b/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx @@ -440,7 +440,7 @@ function loadMocWithAbort(mocList, plotId,setMocError) { try { for(let i=0; (i}> - {!hasMenu ? -
: + {hasMenu && getActivePlotView(visRoot())?.primeIdx ?? -1); const {current:showingStatus}= useRef({oldWhatToShow:undefined}); const chartTableOptions = [{label: 'Table', value: SHOW_TABLE}, {label: 'Chart', value: SHOW_CHART}]; @@ -95,7 +94,9 @@ export function MultiProductChoice({ dataProductsState, dpId, {mayToggle && toolbar} diff --git a/src/firefly/js/voAnalyzer/TableAnalysis.js b/src/firefly/js/voAnalyzer/TableAnalysis.js index 56657a502..cac2c6588 100644 --- a/src/firefly/js/voAnalyzer/TableAnalysis.js +++ b/src/firefly/js/voAnalyzer/TableAnalysis.js @@ -322,6 +322,7 @@ export function getProdTypeGuess(tableOrId, rowIdx) { * @returns {boolean} */ export function isObsCoreLike(tableModel) { + if (!tableModel) return false; const cols = getColumns(tableModel); if (cols.findIndex((c) => get(c, 'utype', '').startsWith(obsPrefix)) >= 0) { return true; diff --git a/src/firefly/js/voAnalyzer/VoDataLinkServDef.js b/src/firefly/js/voAnalyzer/VoDataLinkServDef.js index 15396c4f4..0aae600b0 100644 --- a/src/firefly/js/voAnalyzer/VoDataLinkServDef.js +++ b/src/firefly/js/voAnalyzer/VoDataLinkServDef.js @@ -2,11 +2,19 @@ * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ import {isArray} from 'lodash'; +import {MetaConst} from '../data/MetaConst'; +import {isDefined} from '../util/WebUtil'; +import {makeWorldPt, parseWorldPt} from '../visualize/Point'; +import { + getObsCoreAccessFormat, getObsCoreAccessURL, getObsCoreProdType, getObsCoreSRegion, isDatalinkTable, isObsCoreLike +} from './TableAnalysis'; import { adhocServiceUtype, cisxAdhocServiceUtype, standardIDs, VO_TABLE_CONTENT_TYPE, - SERVICE_DESC_COL_NAMES + SERVICE_DESC_COL_NAMES, RA_UCDs, DEC_UCDs } from './VoConst.js'; -import {columnIDToName, getColumnIdx, getTblRowAsObj} from '../tables/TableUtil.js'; +import { + columnIDToName, getCellValue, getColumnByRef, getColumnIdx, getMetaEntry, getTblRowAsObj +} from '../tables/TableUtil.js'; import {getTableModel} from './VoCoreUtils.js'; @@ -20,9 +28,11 @@ import {getTableModel} from './VoCoreUtils.js'; */ export function analyzeDatalinkRow({semantics = '', localSemantics = '', contentQualifier = '', contentType = ''}) { const isImage = contentType?.toLowerCase() === 'image/fits'; + const maybeImage = contentType?.toLowerCase().includes('fits'); const semL = semantics.toLowerCase(); const locSemL = localSemantics.toLowerCase(); const isThis = semL.includes('#this'); + const isCounterpart = semL.includes('#counterpart'); const isAux = semL === '#auxiliary'; const isGrid = semL.includes('-grid') || (locSemL.includes('-grid') || ( locSemL.includes('#grid'))); const isCutout = semL.includes('cutout') || semL.includes('#cutout') || semL.includes('-cutout') || locSemL.includes('cutout'); @@ -37,9 +47,8 @@ export function analyzeDatalinkRow({semantics = '', localSemantics = '', content const isSimpleImage= isSimpleImageType(contentType); const isDownloadOnly= isDownloadType(contentType); return { - isThis, isImage, isGrid, isAux, isSpectrum, isCutout, rBand, gBand, bBand, - cisxPrimaryQuery, cisxConcurrentQuery, - isTar, isGzip, isSimpleImage, isDownloadOnly + isThis, isCounterpart, isImage, maybeImage, isGrid, isAux, isSpectrum, isCutout, rBand, gBand, bBand, + cisxPrimaryQuery, cisxConcurrentQuery, isTar, isGzip, isSimpleImage, isDownloadOnly, cutoutFullPair:false, }; } @@ -99,6 +108,7 @@ export function getServiceDescriptors(tableOrId, removeAsync = true) { ID, utype, sdSourceTable: table, + positionWP: parseWorldPt(getMetaEntry(table, MetaConst.SEARCH_TARGET, undefined)), title: desc ?? getSDDescription(table, ID) ?? 'Service Descriptor ' + idx, accessURL: params.find(({name}) => name==='accessURL')?.value, standardID: params.find(({name}) => name==='standardID')?.value, @@ -126,13 +136,19 @@ export function getServiceDescriptors(tableOrId, removeAsync = true) { /** * @param {TableModel|String} dataLinkTableOrId - a TableModel or id that is a datalink call result + * @param {TableModel} [sourceObsCoreTbl] + * @param {number} [sourceObsCoreRow] * @return {Array.} */ -export function getDataLinkData(dataLinkTableOrId) { +export function getDataLinkData(dataLinkTableOrId, sourceObsCoreTbl=undefined, sourceObsCoreRow=-1) { const dataLinkTable= getTableModel(dataLinkTableOrId); const {data}= dataLinkTable?.tableData ?? {}; if (!data) return []; - return data.map((r, idx) => { + const sourceObsCoreData= getObsCoreData(sourceObsCoreTbl,sourceObsCoreRow); + const positionWP= parseWorldPt(getMetaEntry(dataLinkTable, MetaConst.SEARCH_TARGET, undefined)); + + const dlDataAry= data + .map((r, idx) => { const tmpR = getTblRowAsObj(dataLinkTable, idx); const rowObj= Object.fromEntries( // convert any null or undefined to empty string Object.entries(tmpR).map(([k,v]) => ([k,v??'']))); @@ -146,14 +162,56 @@ export function getDataLinkData(dataLinkTableOrId) { const idKey= Object.keys(rowObj).find((k) => k.toLowerCase()==='id'); const serDef= getServiceDescriptorForId(dataLinkTable,serviceDefRef,idx); const dlAnalysis= analyzeDatalinkRow({semantics, localSemantics, contentType, contentQualifier}); + const prodTypeHint= contentType || sourceObsCoreData?.dataproduct_type; return { id: rowObj[idKey], contentType, contentQualifier, semantics, localSemantics, url, error_message, - description, size, serviceDefRef, serDef, rowIdx: idx, dlAnalysis, + description, size, serviceDefRef, serDef, rowIdx: idx, dlAnalysis, prodTypeHint, + sourceObsCoreData, relatedDLEntries: {}, positionWP, }; }) - .filter(({url='', serviceDefRef, error_message}) => - serviceDefRef || error_message || url.startsWith('http') || url.startsWith('ftp')); + .filter(({url='', serviceDefRef, serDef, error_message}) => + (serviceDefRef && serDef) || error_message || url.startsWith('http') || url.startsWith('ftp')); + + dlDataAry.forEach( (dlData) => { + const {dlAnalysis:{isThis,isCounterpart,maybeImage,isCutout}, id}= dlData; + if ((isThis || isCounterpart) && maybeImage && !isCutout) { + const foundCutout= dlDataAry.filter( (testData) => testData.id===id && testData.dlAnalysis.isCutout); + if (foundCutout.length===1) setupRelatedCutout(dlData,foundCutout[0]); + // todo in future search for a related region for main image or cutout here + } + }); + return dlDataAry; +} + +function setupRelatedCutout(prim,cutout) { + prim.relatedDLEntries.cutout= cutout; + cutout.relatedDLEntries.fullImage= prim; + prim.dlAnalysis.cutoutFullPair= true; + cutout.dlAnalysis.cutoutFullPair= true; + + //?? todo determine if this is right + if (prim.dlAnalysis.isThis) cutout.dlAnalysis.isThis= true; + if (prim.dlAnalysis.isCounterpart) cutout.dlAnalysis.isCounterpart= true; + if (prim.dlAnalysis.isGrid || cutout.dlAnalysis.isGrid) { + prim.dlAnalysis.isGrid= true; + cutout.dlAnalysis.isGrid= true; + } +} + +export function getObsCoreData(tbl,row) { + + + if (!tbl || row<0) return undefined; + if (!isObsCoreLike(tbl)) return undefined; + const obsCoreData= getTblRowAsObj(tbl, row); + if (!obsCoreData) return undefined; + + obsCoreData.dataproduct_type ??= getObsCoreProdType(tbl,row); + obsCoreData.s_region ??= getObsCoreSRegion(tbl,row); + obsCoreData.access_url ??= getObsCoreAccessURL(tbl,row); + obsCoreData.access_format ??= getObsCoreAccessFormat(tbl,row); + return obsCoreData; } function getServiceDescriptorForId(table, matchId, dataLinkTableRowIdx) { @@ -186,6 +244,39 @@ export function isAnalysisTableDatalink(report) { return hasCorrectCols && part.totalTableRows < 50; // 50 is arbitrary, it is protections from dealing with files that are very big } +export function findWorldPtInServiceDef(serDef,sourceRow) { + if (!serDef) return; + const {serDefParams,sdSourceTable, dataLinkTableRowIdx} = serDef; + const raParam= serDefParams.find( ({UCD=''}) => + RA_UCDs.find( (testUcd) => UCD.toLowerCase().includes(testUcd)) ); + const decParam= serDefParams.find( ({UCD=''}) => + DEC_UCDs.find( (testUcd) => UCD.toLowerCase().includes(testUcd)) ); + if (!raParam && !decParam) return; + + let raVal= raParam.value; + let decVal= decParam.value; + + if (raVal && decVal) return makeWorldPt(raVal,decVal); + if (!sdSourceTable) return; + + const hasDLTable= isDatalinkTable(sdSourceTable); + const hasDLRow= isDefined(dataLinkTableRowIdx); + const hasSourceRow= isDefined(sourceRow); + const row= hasDLTable && hasDLRow ? dataLinkTableRowIdx : hasSourceRow ? sourceRow : undefined; + + if (!raVal && raParam.ref) { + const col = getColumnByRef(sdSourceTable, raParam.ref); + if (col && row > -1) raVal = getCellValue(sdSourceTable, row, col.name); + } + + if (!decVal && decParam.ref) { + const col = getColumnByRef(sdSourceTable, decParam.ref); + if (col && row > -1) decVal = getCellValue(sdSourceTable, row, col.name); + } + + return (raVal && decVal) ? makeWorldPt(raVal,decVal) : undefined; +} + /** * diff --git a/src/firefly/js/voAnalyzer/vo-typedefs.jsdoc b/src/firefly/js/voAnalyzer/vo-typedefs.jsdoc index b73c59835..517e7d9c9 100644 --- a/src/firefly/js/voAnalyzer/vo-typedefs.jsdoc +++ b/src/firefly/js/voAnalyzer/vo-typedefs.jsdoc @@ -39,6 +39,7 @@ /** * @typedef DlAnalysisData * @prop {boolean} isThis + * @prop {boolean} isCounterpart * @prop {boolean} isImage * @prop {boolean} isGrid * @prop {boolean} isAux @@ -53,6 +54,7 @@ * @prop {boolean} isGzip * @prop {boolean} isSimpleImage * @prop {boolean} isDownloadOnly + * @prop {boolean} cutoutFullPair */ @@ -69,9 +71,25 @@ * @prop {String} serviceDefRef * @prop {ServiceDescriptorDef} serDef * @prop {DlAnalysisData} dlAnalysis + * @prop {Object} relatedDLEntries + * @prop {object} relatedRows + * @prop {ObsCoreData} sourceObsCoreData */ +/** + * @typedef ObsCoreData + * + * @prop {string} access_format + * @prop {string} access_url + * @prop {string} dataproduct_subtype + * @prop {string} dataproduct_type + * @prop {string} obs_collection + * @prop {string} obs_title + * @prop {string} s_ra + * @prop {string} s_dec + * @prop {string} s_region + */