diff --git a/src/firefly/js/drawingLayers/HiPSMOC.js b/src/firefly/js/drawingLayers/HiPSMOC.js index 2738972f91..05be7c844f 100644 --- a/src/firefly/js/drawingLayers/HiPSMOC.js +++ b/src/firefly/js/drawingLayers/HiPSMOC.js @@ -156,7 +156,9 @@ function creator(initPayload) { const preloadedTbl= tablePreloaded && getTblById(tbl_id); drawingDef.color = preloadedTbl?.tableMeta?.[MetaConst.DEFAULT_COLOR] ?? defColors[mocGroupDefColorId] ?? color; const defStyle= getAppOptions().hips.mocDefaultStyle ?? 'AUTO'; - const inStyleStr= getMetaEntry(preloadedTbl, MetaConst.MOC_DEFAULT_STYLE, defStyle).toLowerCase(); + const inStyleStr= + initPayload.mocStyle?.toLowerCase() ?? + getMetaEntry(preloadedTbl, MetaConst.MOC_DEFAULT_STYLE, defStyle).toLowerCase(); switch (inStyleStr) { case 'moc tile outline': case 'tile outline': diff --git a/src/firefly/js/drawingLayers/HiPSMOCUI.jsx b/src/firefly/js/drawingLayers/HiPSMOCUI.jsx index 61a792359f..c8510d693f 100644 --- a/src/firefly/js/drawingLayers/HiPSMOCUI.jsx +++ b/src/firefly/js/drawingLayers/HiPSMOCUI.jsx @@ -8,13 +8,9 @@ import {object} from 'prop-types'; import {RadioGroupInputFieldView} from '../ui/RadioGroupInputFieldView.jsx'; import {Style} from '../visualize/draw/DrawingDef.js'; import {dispatchModifyCustomField} from '../visualize/DrawLayerDispatch'; +import {mocUIDisplayOptions} from '../visualize/HiPSMocUtil'; + -const options= [ - {label: 'Outline', value: Style.DESTINATION_OUTLINE.key}, - {label: 'Fill', value: Style.FILL.key}, - {label: 'Auto', value: Style.AUTO.key}, - {label: 'MOC Tile Outline', value: Style.STANDARD.key}, -]; export const getUIComponent = (drawLayer,pv) => ; @@ -23,7 +19,7 @@ function HiPSMOCUI({drawLayer:dl,pv}) { const style = dl?.requestedStyle ?? dl?.mocStyle?.[pv.plotId] ?? dl.drawingDef?.style ?? Style.DESTINATION_OUTLINE; return ( - changeMocPref(dl,pv,ev.target.value, style.key)} /> ); diff --git a/src/firefly/js/metaConvert/vo/ServDescProducts.js b/src/firefly/js/metaConvert/vo/ServDescProducts.js index 9102f4180f..2e88987e1c 100644 --- a/src/firefly/js/metaConvert/vo/ServDescProducts.js +++ b/src/firefly/js/metaConvert/vo/ServDescProducts.js @@ -2,11 +2,12 @@ import {isEmpty, isNumber, isUndefined} from 'lodash'; import {getComponentState} from '../../core/ComponentCntlr.js'; import {getCellValue} from '../../tables/TableUtil.js'; import {CONTEXT_PARAMS_STR, makeCircleString} from '../../ui/dynamic/DynamicUISearchPanel'; -import {hasAnySpacial, isSIAStandardID, sdToFieldDefAry} from '../../ui/dynamic/ServiceDefTools'; +import {hasAnySpacial, sdToFieldDefAry} from '../../ui/dynamic/ServiceDefTools'; import {findCutoutTarget, getCutoutErrorStr, getCutoutSize, setCutoutSize} from '../../ui/tap/Cutout'; import {PlotAttribute} from '../../visualize/PlotAttribute'; import {isCatalog, isObsCoreLike} from '../../voAnalyzer/TableAnalysis'; import {CUTOUT_UCDs, DEC_UCDs, RA_UCDs} from '../../voAnalyzer/VoConst'; +import {isSIAStandardID} from '../../voAnalyzer/VoCoreUtils'; import {findWorldPtInServiceDef, isDataLinkServiceDesc} from '../../voAnalyzer/VoDataLinkServDef.js'; import {isDefined} from '../../util/WebUtil.js'; diff --git a/src/firefly/js/templates/common/ttFeatureWatchers.js b/src/firefly/js/templates/common/ttFeatureWatchers.js index 32f9642e72..4704e9673c 100644 --- a/src/firefly/js/templates/common/ttFeatureWatchers.js +++ b/src/firefly/js/templates/common/ttFeatureWatchers.js @@ -16,13 +16,13 @@ import {useFieldGroupValue, useStoreConnector} from 'firefly/ui/SimpleComponent' import {findCutoutTarget, getCutoutSize, ROW_POSITION, tblIdToKey,} from 'firefly/ui/tap/Cutout'; import {getTableModel} from 'firefly/voAnalyzer/VoCoreUtils'; import {fetchSemanticList} from 'firefly/metaConvert/vo/DatalinkFetch'; -import {checkForDatalinkServDesc} from 'firefly/ui/dynamic/ServiceDefTools'; import {CheckboxGroupInputField, SelectAllCheckbox} from 'firefly/ui/CheckboxGroupInputField'; import {FormControl, FormLabel, Stack, Typography} from '@mui/joy'; import {ToolbarButton} from 'firefly/ui/ToolbarButton'; import {FieldGroup} from 'firefly/ui/FieldGroup'; import {getFieldVal} from 'firefly/fieldGroup/FieldGroupUtils'; import {makeFoVString} from 'firefly/visualize/ZoomUtil'; +import {checkForDatalinkServDesc} from '../../voAnalyzer/VoDataLinkServDef'; export const getAllStartIds= ()=> [ getMocWatcherDef().id, diff --git a/src/firefly/js/ui/dynamic/CisxSerDescUtil.js b/src/firefly/js/ui/dynamic/CisxSerDescUtil.js new file mode 100644 index 0000000000..84023fc6f1 --- /dev/null +++ b/src/firefly/js/ui/dynamic/CisxSerDescUtil.js @@ -0,0 +1,134 @@ +import {isValidFullUrl} from '../../util/WebUtil'; +import CoordinateSys from '../../visualize/CoordSys'; +import {makeWorldPt} from '../../visualize/Point'; + + +/* + * ------------------------------------- + * Supports the processing of IRSA extension of service descriptor: { + if (!name) return false; + const key = name.toLowerCase(); + return key === 'moc' || key.match(/moc\d+$/i) || itemsList.some((s) => key.startsWith(s)); + }) + .reduce((allMocs, {name, value, desc}) => { + const key = itemsList.find((k) => name.startsWith(k)); + const id= name.substring(key?.length ?? 'moc'.length); + if (!allMocs[id]) allMocs[id] = {}; + if (key) { + allMocs[id][translateMocXml[key]] = value; + } else { + allMocs[id].mocUrl= value; + allMocs[id].title= desc; + } + return allMocs; + }, {}); + return Object.values(allMocsObj); +} + +/** + * + * @param {Object} cisxUI + * @param {number} defaultMaxMOCFetchDepth + * @return {SearchAreaInfo} + */ +export function makeSearchAreaInfo(cisxUI, defaultMaxMOCFetchDepth) { + if (!cisxUI) return; + const tmpObj = cisxUI.reduce((obj, {name, value, UCD}) => { + switch (name) { + case 'hips_initial_fov': + obj[name] = Number(value); + break; + case 'hips_initial_dec': + case 'hips_initial_ra': + obj[name] = Number(value); + obj.ptIsGalactic = UCD?.includes('galactic'); + break; + case 'polygon_examples': + case 'examples': + obj[name] = makeExamples(value); + break; + default: + if (!name?.startsWith('moc')) obj[name] = value; + break; + } + return obj; + }, {}); + + // const mocList= getMOCList(findAndTranslateMocParameters(cisxUI)); + const mocList= findAndTranslateMocParameters(cisxUI); + const {hips_initial_ra, hips_initial_dec, hips_frame, ptIsGalactic} = tmpObj; + const hipsProjCsys = hips_frame?.trim().toLowerCase() === 'galactic' ? CoordinateSys.GALACTIC : CoordinateSys.EQ_J2000; + const ptCsys = ptIsGalactic ? CoordinateSys.GALACTIC : CoordinateSys.EQ_J2000; + const centerWp = makeWorldPt(hips_initial_ra, hips_initial_dec, ptCsys); + return { + ...tmpObj, mocList, centerWp, + coordinateSys: hipsProjCsys.toString(), maxFetchDepth: defaultMaxMOCFetchDepth + }; +} + +function makeExamples(inExample) { + if (!inExample) return {targetPanelExampleRow1: undefined, targetPanelExampleRow2: undefined}; + const examples = inExample.split('|'); + if (examples?.length > 1) { + const cnt = examples.length; + return { + targetPanelExampleRow1: examples.slice(0, Math.trunc(cnt / 2)), + targetPanelExampleRow2: examples.slice(Math.trunc(cnt / 2)) + }; + } else { + return {targetPanelExampleRow1: [inExample], targetPanelExampleRow2: []}; + } +} + +/** + * @param {QueryAnalysis|ServiceDescriptorDef} qAnaOrSd - accept a QueryAnalysis or a ServiceDescriptorDef + * @return {CISXui|Array} ui parameters or an empty array + */ +export function getCisxUI(qAnaOrSd) { + if (!qAnaOrSd) return []; + if (qAnaOrSd.primarySearchDef) { // is QueryAnalysis + return qAnaOrSd.primarySearchDef[0]?.serviceDef?.cisxUI ?? []; + } else if (qAnaOrSd.accessURL) { // is ServiceDescriptorDef + return qAnaOrSd.cisxUI ?? []; + } + return []; +} + +/** + * @param {QueryAnalysis|ServiceDescriptorDef} qAnaOrSd - accept a QueryAnalysis or a ServiceDescriptorDef + * @param {String} name + * @return the value + */ +export function getCisxUIValue(qAnaOrSd, name) { + return getCisxUI(qAnaOrSd).find((e) => e.name === name)?.value; +} + +/** + * @param {QueryAnalysis|ServiceDescriptorDef} qAnaOrSd - accept a QueryAnalysis or a ServiceDescriptorDef + * @param {String} name + * @return the UCD + */ +export function getCisxUIUCD(qAnaOrSd, name) { + return getCisxUI(qAnaOrSd).find((e) => e.name === name)?.UCD; +} \ No newline at end of file diff --git a/src/firefly/js/ui/dynamic/DLGenAnalyzeSearch.js b/src/firefly/js/ui/dynamic/DLGenAnalyzeSearch.js index 70f32f2722..af65bb37bd 100644 --- a/src/firefly/js/ui/dynamic/DLGenAnalyzeSearch.js +++ b/src/firefly/js/ui/dynamic/DLGenAnalyzeSearch.js @@ -1,18 +1,20 @@ -import {isEmpty} from 'lodash'; +import {isArray, isEmpty} from 'lodash'; import {MetaConst} from '../../data/MetaConst.js'; -import {makeTblRequest} from '../../tables/TableRequestUtil'; +import {sortInfoString} from '../../tables/SortInfo'; +import {makeFileRequest, makeTblRequest, setNoCache} from '../../tables/TableRequestUtil'; import {dispatchTableSearch} from '../../tables/TablesCntlr.js'; import {getMetaEntry} from '../../tables/TableUtil.js'; import {Logger} from '../../util/Logger.js'; +import {tokenSub} from '../../util/WebUtil'; import {CONE_CHOICE_KEY, POLY_CHOICE_KEY} from '../../visualize/ui/CommonUIKeys.js'; +import {isCisxTapStandardID, isSIAStandardID, isSSAStandardID} from '../../voAnalyzer/VoCoreUtils'; import {getDataLinkData} from '../../voAnalyzer/VoDataLinkServDef.js'; +import {getCisxUIValue} from './CisxSerDescUtil'; import {CIRCLE, POINT, POLYGON} from './DynamicDef.js'; import { convertCircleToPointArea, convertPointAreaToCircle, isCircleSearch, isPointAreaSearch, isPolySearch } from './DynamicUISearchPanel.jsx'; -import { - findFieldDefType, isSIAStandardID, makeServiceDescriptorSearchRequest, sdToFieldDefAry -} from './ServiceDefTools.js'; +import {findFieldDefType, sdToFieldDefAry} from './ServiceDefTools.js'; /** @@ -47,7 +49,77 @@ export function analyzeQueries(tbl_id) { let upTblCnt=1; -export function makeAllSearchRequest(request, siaConstraints, primeSd, concurrentSDAry, primaryFdAry, extraPrimaryMeta, uploadSiaExtParams) { +let tblCnt = 1; + +function makeServiceDescriptorSearchRequest(request, siaConstraints = [], serviceDescriptor, extraMeta = {}) { + const {standardID = '', accessURL, utype, serDefParams, title, cisxUI = []} = serviceDescriptor; + const hiddenColumns = cisxUI.find((e) => e.name === 'hidden_columns')?.value; + const tblSortOrder = cisxUI.find((e) => e.name === 'table_sort_order')?.value; + const MAXREC = 50000; + const tblTitle = `${title} - ${tblCnt++}`; + + const hideObj = hiddenColumns ? + Object.fromEntries(hiddenColumns.split(',').map((c) => [`col.${c}.visibility`, 'hide'])) : {}; + + const sAry = tblSortOrder?.match(/([^,]+),(.+)/); + let sortObj = {}; + if (sAry) { + const [, dir, sortBy] = sAry; + sortObj = {sortInfo: sortInfoString(sortBy, dir?.toUpperCase() === 'ASC')}; + } + + const options = {...sortObj, META_INFO: {...hideObj, ...extraMeta}}; + const requestAsArray = Object.entries(request).reduce((ary, [k, v]) => { + isArray(v) + ? v.forEach((vEntry) => ary.push([k, vEntry])) + : ary.push([k, v]); + return ary; + }, []); + + if (isSIAStandardID(standardID)) { + const reqParams = new URLSearchParams(requestAsArray); + const siaParams = new URLSearchParams(); + siaConstraints.forEach((s) => { + const [k, v] = s.split('='); + if (k && v) siaParams.append(k, v); + }); + const params = new URLSearchParams([...reqParams, ...siaParams]); + const url = params.size ? accessURL + '?' + params.toString() : accessURL; + return makeFileRequest(tblTitle, url, undefined, options); //todo- figure out title + } else if (isSSAStandardID(standardID)) { + const url = accessURL + '?' + new URLSearchParams(requestAsArray).toString(); + return makeFileRequest(tblTitle, url, undefined, options); //todo- figure out title + } else if (isCisxTapStandardID(standardID, utype)) { + const doAsync = standardID.toLowerCase().includes('async'); + const query = serDefParams.find(({name}) => name === 'QUERY')?.value; + const finalQuery = tokenSub(request, query); + let serviceUrl = accessURL; + if (accessURL.endsWith('/sync')) serviceUrl = accessURL.substring(0, accessURL.length - 5); + if (accessURL.endsWith('/async')) serviceUrl = accessURL.substring(0, accessURL.length - 6); + + if (!query) return; + if (doAsync) { + const asyncReq = makeTblRequest('AsyncTapQuery', tblTitle, { + serviceUrl, + QUERY: finalQuery, + MAXREC + }, options); + setNoCache(asyncReq); + return asyncReq; + } else { + const serParam = new URLSearchParams({QUERY: finalQuery, REQUEST: 'doQuery', LANG: 'ADQL', MAXREC}); + const completeUrl = serviceUrl + '/sync?' + serParam.toString(); + return makeFileRequest(title, completeUrl, undefined, options); //todo- figure out title + } + + } else { + //todo: we should to call file analysis first + const url = accessURL + '?' + new URLSearchParams(requestAsArray).toString(); + return makeFileRequest(tblTitle, url, undefined, options); //todo- figure out title + } +} + +function makeAllSearchRequest(request, siaConstraints, primeSd, concurrentSDAry, primaryFdAry, extraPrimaryMeta, uploadSiaExtParams) { if (uploadSiaExtParams) { const {title,accessURL} = primeSd; const tblTitle= `${title} - upload ${upTblCnt++}`; @@ -148,29 +220,6 @@ export function isSpatialTypeSupported(serviceDef, spacialType) { */ -/** - * @param {QueryAnalysis|ServiceDescriptorDef} qAnaOrSd - accept a QueryAnalysis or a ServiceDescriptorDef - * @return {CISXui|Array} ui parameters or an empty array - */ -export function getCisxUI(qAnaOrSd) { - if (!qAnaOrSd) return []; - if (qAnaOrSd.primarySearchDef) { // is QueryAnalysis - return qAnaOrSd.primarySearchDef[0]?.serviceDef?.cisxUI ?? []; - } else if (qAnaOrSd.accessURL) { // is ServiceDescriptorDef - return qAnaOrSd.cisxUI ?? []; - } - return []; -} - -export function getCisxUIValue(qAnaOrSd, name) { - return getCisxUI(qAnaOrSd).find((e) => e.name === name)?.value; -} - -export function getCisxUIUCD(qAnaOrSd, name) { - return getCisxUI(qAnaOrSd).find((e) => e.name === name)?.UCD; -} - - export function supportsUpload(qAna, standardID, useConcurrent= false) { const hasUpload= getCisxUIValue(qAna,'IRSA_SIA_upload_extension') && isSIAStandardID(standardID); if (!hasUpload) return false; diff --git a/src/firefly/js/ui/dynamic/DLGeneratedDropDown.js b/src/firefly/js/ui/dynamic/DLGeneratedDropDown.js index 99a9e364d1..eef030f9b4 100644 --- a/src/firefly/js/ui/dynamic/DLGeneratedDropDown.js +++ b/src/firefly/js/ui/dynamic/DLGeneratedDropDown.js @@ -25,7 +25,7 @@ import {FieldGroup} from '../FieldGroup.jsx'; import {showInfoPopup} from '../PopupUtil.jsx'; import {useStoreConnector} from '../SimpleComponent.jsx'; import { - analyzeQueries, getCisxUI, getCisxUIUCD, getCisxUIValue, handleSearch, supportsUpload + analyzeQueries, handleSearch, supportsUpload } from './DLGenAnalyzeSearch.js'; import {SideBarAnimation, SideBarTable} from './DLuiDecoration.jsx'; import {DLuiServDescPanel, DLuiTabView} from './DLuiServDescPanel.jsx'; @@ -34,7 +34,9 @@ import {AREA, CIRCLE, CONE_AREA_KEY, POINT, POSITION, RANGE} from './DynamicDef. import {convertRequest, DEFER_TO_CONTEXT, findTargetFromRequest} from './DynamicUISearchPanel.jsx'; import {getSpacialSearchType, hasValidSpacialSearch} from './DynComponents.jsx'; import {confirmDLMenuItem} from './FetchDatalinkTable.js'; -import { getStandardIdType, ingestInitArgs, makeSearchAreaInfo, sdToFieldDefAry } from './ServiceDefTools.js'; +import {getStandardIdType} from '../../voAnalyzer/VoCoreUtils'; +import {getCisxUI, getCisxUIUCD, getCisxUIValue, makeSearchAreaInfo} from './CisxSerDescUtil'; +import {ingestInitArgs, sdToFieldDefAry} from './ServiceDefTools.js'; export const DL_UI_LIST= 'DL_UI_LIST'; @@ -342,7 +344,7 @@ function DLGeneratedTableSearch({currentTblId, qAna, groupKey, initArgs, sideBar alignHiPS(currentTblId,qAna,groupKey, fds); }, [currentTblId, groupKey, qAna, searchObjFds, tabsKey]); - const isAllSky= toBoolean(getCisxUI(qAna)?.find( (e) => e.name==='data_covers_allsky')?.value); + const isAllSky= toBoolean(getCisxUIValue(qAna,'data_covers_allsky')); const docRows= qAna?.urlRows.filter( ({semantic}) => semantic?.toLowerCase().endsWith('documentation')); const submitSearch= (request,siaCtx) => diff --git a/src/firefly/js/ui/dynamic/DLuiServDescPanel.jsx b/src/firefly/js/ui/dynamic/DLuiServDescPanel.jsx index fecc6a8df2..44c2495f17 100644 --- a/src/firefly/js/ui/dynamic/DLuiServDescPanel.jsx +++ b/src/firefly/js/ui/dynamic/DLuiServDescPanel.jsx @@ -2,18 +2,18 @@ import {Box, Link, Stack, Typography} from '@mui/joy'; import {array, arrayOf, bool, func, node, object, oneOfType, string} from 'prop-types'; import React, {useContext, useEffect, useState} from 'react'; import {CONE_CHOICE_KEY} from '../../visualize/ui/CommonUIKeys.js'; +import {isSIAStandardID} from '../../voAnalyzer/VoCoreUtils'; import {CheckboxGroupInputField} from '../CheckboxGroupInputField.jsx'; import {FieldGroupCtx} from '../FieldGroup'; import {FieldGroupTabs, Tab} from '../panel/TabPanel'; import {showInfoPopup} from '../PopupUtil.jsx'; import {useFieldGroupValue} from '../SimpleComponent.jsx'; import {getServiceMetaOptions, loadSiaV2Meta, makeObsCoreMetadataModel} from '../tap/SiaUtil'; -import {getCisxUIValue, hasSpatialTypes, isSpatialTypeSupported, supportsUpload} from './DLGenAnalyzeSearch.js'; +import {hasSpatialTypes, isSpatialTypeSupported, supportsUpload} from './DLGenAnalyzeSearch.js'; import {DLSearchTitle} from './DLuiDecoration'; import {CONE_AREA_KEY} from './DynamicDef.js'; import {DynLayoutPanelTypes} from './DynamicUISearchPanel'; import {ConstraintContext} from '../tap/Constraints'; -import {isSIAStandardID} from './ServiceDefTools'; const HIPS_PLOT_ID= 'dlGeneratedHipsPlotId'; diff --git a/src/firefly/js/ui/dynamic/ServiceDefTools.js b/src/firefly/js/ui/dynamic/ServiceDefTools.js index 91d9343e37..dc6c7c2060 100644 --- a/src/firefly/js/ui/dynamic/ServiceDefTools.js +++ b/src/firefly/js/ui/dynamic/ServiceDefTools.js @@ -1,21 +1,14 @@ import {isArray, isNumber, isString} from 'lodash'; import {ReservedParams} from '../../api/WebApi.js'; import {sprintf} from '../../externalSource/sprintf.js'; -import {sortInfoString} from '../../tables/SortInfo.js'; -import {makeFileRequest, makeTblRequest, setNoCache} from '../../tables/TableRequestUtil.js'; -import {cisxAdhocServiceUtype, standardIDs} from '../../voAnalyzer/VoConst.js'; -import {isValidFullUrl, splitByWhiteSpace, tokenSub} from '../../util/WebUtil.js'; -import CoordinateSys from '../../visualize/CoordSys.js'; +import {splitByWhiteSpace} from '../../util/WebUtil.js'; import {makeWorldPt, parseWorldPt} from '../../visualize/Point.js'; +import {isSIAStandardID} from '../../voAnalyzer/VoCoreUtils'; import { AREA, CHECKBOX, CIRCLE, ENUM, FLOAT, INT, makeAreaDef, makeCircleDef, makeEnumDef, makeFloatDef, makeIntDef, - makeObsCoreOps, - makePointDef, makePolygonDef, makeRangeDef, makeTargetDef, makeUnknownDef, makeWavelengthDef, - POINT, POLYGON, POSITION, RANGE, UNKNOWN + makeObsCoreOps, makePointDef, makePolygonDef, makeRangeDef, makeTargetDef, makeUnknownDef, makeWavelengthDef, POINT, + POLYGON, POSITION, RANGE, UNKNOWN } from './DynamicDef.js'; -import {getServiceDescriptors, isDataLinkServiceDesc} from 'firefly/voAnalyzer/VoDataLinkServDef'; - - /** * @param {Array.} fieldDefAry @@ -36,21 +29,6 @@ export const hasAnySpacial= (fieldDefAry) => hasType(fieldDefAry,POSITION) || hasType(fieldDefAry,POINT) || hasType(fieldDefAry,AREA); -export const isSIAStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.sia); -export const isSSAStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.ssa); -export const isSODAStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.soda); -export const isTAPStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.tap); - -export function getStandardIdType(standardID) { - if (isSIAStandardID(standardID)) return standardIDs.sia; - if (isSSAStandardID(standardID)) return standardIDs.ssa; - if (isSODAStandardID(standardID)) return standardIDs.soda; - if (isTAPStandardID(standardID)) return standardIDs.tap; -} - -export const isCisxTapStandardID = (standardID, utype) => - standardID.toLowerCase().startsWith(standardIDs.tap) && utype.toLowerCase() === cisxAdhocServiceUtype; - const isCircleField = ({type, arraySize, xtype = '', units = ''}) => isFloating(type) && Number(arraySize) === 3 && isXtype(xtype,CIRCLE) && isDeg(units); @@ -116,6 +94,12 @@ const isNumberField = ({type, minValue, maxValue, value}) => (value || minValue || maxValue) && (!isNaN(Number(value)) || !isNaN(Number(minValue)) || !isNaN(Number(maxValue))); +/** + * + * @param {Array.} serDefParams + * @param {SearchAreaInfo} searchAreaInfo + * @return {Object} + */ function prefilterRADec(serDefParams, searchAreaInfo = {}) { const foundRa= findParamByUCDOrName(serDefParams,'pos.eq.ra', 'ra'); const foundDec= findParamByUCDOrName(serDefParams,'pos.eq.dec', 'dec'); @@ -129,7 +113,7 @@ function prefilterRADec(serDefParams, searchAreaInfo = {}) { raKey: foundRa.name, decKey: foundDec.name, hipsFOVInDeg: searchAreaInfo.hips_initial_fov, - mocList: getMOCList(searchAreaInfo), + mocList: searchAreaInfo.mocList, hipsUrl: searchAreaInfo.HiPS, coordinateSys: searchAreaInfo.coordinateSys, targetPanelExampleRow1: searchAreaInfo.examples ? searchAreaInfo.examples.split('|') : undefined @@ -138,7 +122,6 @@ function prefilterRADec(serDefParams, searchAreaInfo = {}) { } function prefilterWavelength(serDefParams, standardID) { - // const foundWlMin = findParamByUCDAndName(serDefParams,'em.wl;stat.min','em_min'); const foundWlBand = findParamByUCDAndName(serDefParams,'em.wl','BAND'); if (!isSIAStandardID(standardID) || !foundWlBand?.name) { return {filteredParams: serDefParams, wlDef: undefined}; @@ -181,31 +164,30 @@ function prefilterObsCoreOps(serDefParams, standardID) { return {filteredParams, obsDef}; } +/** + * + * @param {Array.} serDefParams + * @param {String} UCD + * @param {String} name + * @return {ServiceDescriptorInputParam} + */ function findParamByUCDOrName(serDefParams, UCD, name){ const p= serDefParams.find((aParam) => aParam.UCD === UCD); if (p) return p; return serDefParams.find((aParam) => aParam.name === name); } +/** + * + * @param {Array.} serDefParams + * @param {String} UCD + * @param {String} name + * @return {ServiceDescriptorInputParam} + */ function findParamByUCDAndName(serDefParams, UCD, name){ return serDefParams.find((aParam) => aParam.UCD === UCD && aParam.name === name); } -function makeExamples(inExample) { - if (!inExample) return {targetPanelExampleRow1:undefined, targetPanelExampleRow2:undefined}; - const examples = inExample.split('|'); - if (examples?.length>1) { - const cnt= examples.length; - return { - targetPanelExampleRow1: examples.slice(0,Math.trunc(cnt/2)), - targetPanelExampleRow2: examples.slice(Math.trunc(cnt/2)) - }; - } - else { - return { targetPanelExampleRow1: [inExample], targetPanelExampleRow2: [] }; - } -} - /** * * @param {Object} p @@ -277,26 +259,6 @@ function makeFieldDef({serDefParam, sRegion, searchAreaInfo, hidePredefinedStrin } } -/** - * - * @param {SearchAreaInfo} searchAreaInfo - * @return {Array.<{mocUrl: String, mocColor: String, title:String}>|undefined} - */ -function getMOCList(searchAreaInfo) { - const mocAry= Object.keys(searchAreaInfo) - .filter( (k) => k.toLowerCase()==='moc' || k.match(/moc\d+$/i) ) - .map( (k) => { - const cnt= k.substring(3,k.length); - return { - mocUrl : searchAreaInfo[k], - title : searchAreaInfo['mocDesc'+cnt] ?? 'MOC'+cnt, - mocColor: searchAreaInfo['mocColor'+cnt], - maxFetchDepth: searchAreaInfo['maxFetchDepth'+cnt] - }; - }) - .filter((entry) => isValidFullUrl(entry.mocUrl) ); - return mocAry.length ? mocAry : undefined; -} function doMakeUnknownDef(serDefParam, hidePredefinedStringFields) { const {value='', name, desc: tooltip, units = ''} = serDefParam; @@ -322,14 +284,12 @@ function doMakeEnumDef(serDefParam) { function doMakePolygonField(serDefParam, sRegion, searchAreaInfo) { const {name, desc: tooltip, units = ''} = serDefParam; - const {targetPanelExampleRow1,targetPanelExampleRow2}= makeExamples(searchAreaInfo?.polygon_examples); const {value} = getPolygonInfo(serDefParam); return makePolygonDef({key: name, desc: name, tooltip, units, initValue: value, sRegion, - targetPanelExampleRow1,targetPanelExampleRow2}); + ...searchAreaInfo?.polygon_examples}); } function doMakeCircleDef(serDefParam, sRegion, searchAreaInfo, hipsUrl, fovSize) { - const {targetPanelExampleRow1,targetPanelExampleRow2}= makeExamples(searchAreaInfo?.examples ); const {name, desc: tooltip, units = ''} = serDefParam; const {wpt: centerPt, radius, minValue, maxValue} = getCircleInfo(serDefParam); const hipsFOVInDeg= searchAreaInfo?.hips_initial_fov ?? fovSize ?? radius * 2 + radius * .2; @@ -342,14 +302,12 @@ function doMakeCircleDef(serDefParam, sRegion, searchAreaInfo, hipsUrl, fovSize) hipsFOVInDeg, coordinateSys: searchAreaInfo?.coordinateSys, sRegion, - mocList: getMOCList(searchAreaInfo), - targetPanelExampleRow1, - targetPanelExampleRow2 + mocList: searchAreaInfo.mocList, + ...searchAreaInfo.examples, }); } function doMakePointDef(serDefParam, sRegion, searchAreaInfo, hipsUrl, fovSize) { - const {targetPanelExampleRow1,targetPanelExampleRow2}= makeExamples(searchAreaInfo?.examples ); const {name, desc: tooltip, units = ''} = serDefParam; return makePointDef({ key:name, desc: name, tooltip, units, @@ -358,8 +316,8 @@ function doMakePointDef(serDefParam, sRegion, searchAreaInfo, hipsUrl, fovSize) centerPt: getPointInfo(serDefParam), hipsFOVInDeg: searchAreaInfo?.hips_initial_fov ?? fovSize ?? 2, coordinateSys: searchAreaInfo?.coordinateSys, - mocList: getMOCList(searchAreaInfo), - sRegion, targetPanelExampleRow1, targetPanelExampleRow2 + mocList: searchAreaInfo.mocList, + sRegion, ...searchAreaInfo.examples, }); } @@ -414,130 +372,6 @@ function doMakeNumberDef(serDefParam, hidePredefinedStringFields) { } -/** - * - * @param {Object} cisxUI - * @param {number} defaultMaxMOCFetchDepth - * @return {SearchAreaInfo} - */ -export function makeSearchAreaInfo(cisxUI, defaultMaxMOCFetchDepth) { - if (!cisxUI) return; - const tmpObj = cisxUI.reduce((obj, {name, value, UCD}) => { - switch (name) { - case 'hips_initial_fov': - obj[name] = Number(value); - break; - case 'hips_initial_dec': - case 'hips_initial_ra': - obj[name] = Number(value); - obj.ptIsGalactic= UCD?.includes('galactic'); - break; - default: - if (!name?.startsWith('moc')) obj[name] = value; - break; - } - return obj; - }, {}); - - const mocsObj= findMocs(cisxUI); - const {hips_initial_ra, hips_initial_dec, hips_frame, ptIsGalactic} = tmpObj; - const hipsProjCsys = hips_frame?.trim().toLowerCase()==='galactic' ? CoordinateSys.GALACTIC : CoordinateSys.EQ_J2000; - const ptCsys= ptIsGalactic ? CoordinateSys.GALACTIC : CoordinateSys.EQ_J2000; - const centerWp = makeWorldPt(hips_initial_ra, hips_initial_dec, ptCsys); - return {...tmpObj, ...mocsObj, centerWp, - coordinateSys: hipsProjCsys.toString(), maxFetchDepth:defaultMaxMOCFetchDepth}; -} - - -function findMocs(cisxUI) { - const mocColor= cisxUI.filter( (obj) => obj?.name?.toLowerCase().startsWith('moc_color')); - const moc= cisxUI.filter( (obj) => obj?.name?.toLowerCase()==='moc' || obj?.name?.match(/moc\d+$/i) ); - const combinedObj= [...mocColor,...moc].reduce( (obj, {name, value, desc}) => { - if (name.startsWith('moc_color')) { - obj['mocColor'+ name.substring(9,name.length)]= value; - } - else { - obj[name]= value; - obj['mocDesc'+ name.substring(3,name.length)] = desc; - } - return obj; - },{}); - return combinedObj; -} - - - - -let tblCnt=1; - -export function makeServiceDescriptorSearchRequest(request, siaConstraints=[],serviceDescriptor, extraMeta={}) { - const {standardID = '', accessURL, utype, serDefParams, title, cisxUI=[]} = serviceDescriptor; - const hiddenColumns= cisxUI.find( (e) => e.name==='hidden_columns')?.value; - const tblSortOrder= cisxUI.find( (e) => e.name==='table_sort_order')?.value; - const MAXREC = 50000; - const tblTitle= `${title} - ${tblCnt++}`; - - const hideObj= hiddenColumns ? - Object.fromEntries(hiddenColumns.split(',').map((c) => [ `col.${c}.visibility`, 'hide'] )) : {}; - - const sAry= tblSortOrder?.match(/([^,]+),(.+)/); - let sortObj= {}; - if (sAry) { - const [,dir,sortBy] = sAry; - sortObj= {sortInfo: sortInfoString(sortBy, dir?.toUpperCase()==='ASC')}; - } - - const options= {...sortObj, META_INFO: { ...hideObj, ...extraMeta }}; - const requestAsArray= Object.entries(request).reduce( (ary, [k,v]) => { - isArray(v) - ? v.forEach( (vEntry) => ary.push([k,vEntry])) - : ary.push([k,v]) ; - return ary; - }, []); - - if (isSIAStandardID(standardID)) { - const reqParams= new URLSearchParams(requestAsArray); - const siaParams= new URLSearchParams(); - siaConstraints.forEach( (s) => { - const [k,v]= s.split('='); - if (k && v) siaParams.append(k,v); - }); - const params= new URLSearchParams([...reqParams, ...siaParams]); - const url= params.size ? accessURL+'?'+params.toString() : accessURL; - return makeFileRequest(tblTitle, url, undefined, options); //todo- figure out title - } - else if (isSSAStandardID(standardID)) { - const url = accessURL + '?' + new URLSearchParams(requestAsArray).toString(); - return makeFileRequest(tblTitle, url, undefined, options); //todo- figure out title - } - else if (isCisxTapStandardID(standardID, utype)) { - const doAsync = standardID.toLowerCase().includes('async'); - const query = serDefParams.find(({name}) => name === 'QUERY')?.value; - const finalQuery = tokenSub(request, query); - let serviceUrl = accessURL; - if (accessURL.endsWith('/sync')) serviceUrl = accessURL.substring(0, accessURL.length - 5); - if (accessURL.endsWith('/async')) serviceUrl = accessURL.substring(0, accessURL.length - 6); - - if (!query) return; - if (doAsync) { - const asyncReq = makeTblRequest('AsyncTapQuery', tblTitle, {serviceUrl, QUERY: finalQuery, MAXREC}, options); - setNoCache(asyncReq); - return asyncReq; - } else { - const serParam = new URLSearchParams({QUERY: finalQuery, REQUEST: 'doQuery', LANG: 'ADQL', MAXREC}); - const completeUrl = serviceUrl + '/sync?' + serParam.toString(); - return makeFileRequest(title, completeUrl,undefined, options); //todo- figure out title - } - - } - else { - //todo: we should to call file analysis first - const url = accessURL + '?' + new URLSearchParams(requestAsArray).toString(); - return makeFileRequest(tblTitle, url, undefined,options); //todo- figure out title - } -} - - /** * * @param {Array.} fdAry @@ -605,32 +439,3 @@ export function ingestInitArgs(fdAry, args) { }); } -//check for and return a datalink service descriptor url and input params, if one is found -export function checkForDatalinkServDesc(tblModel) { - const serviceDescriptors = getServiceDescriptors(tblModel); - if (!serviceDescriptors) return null; - - for (const sd of serviceDescriptors) { - //serviceDescriptors.forEach((sd) => { - const isDatalinkSerDesc = isDataLinkServiceDesc(sd); - if (isDatalinkSerDesc) { - const productUrl = { - accessURL: sd?.accessURL || '', - inputParams: {} - }; - - if (sd?.serDefParams) { - for (const param of sd.serDefParams) { - productUrl.inputParams[param?.name] = {value: param.value, ref: param.ref}; - } - } - - //return only when valid datalink access url is found - if (productUrl.accessURL || Object.keys(productUrl.inputParams).length > 0) { - return productUrl; - } - - } - } - return null; //no valid datalink service descriptor found -} diff --git a/src/firefly/js/ui/dynamic/ServiceDescriptorPanel.jsx b/src/firefly/js/ui/dynamic/ServiceDescriptorPanel.jsx index c4b3c82a28..46bfea7722 100644 --- a/src/firefly/js/ui/dynamic/ServiceDescriptorPanel.jsx +++ b/src/firefly/js/ui/dynamic/ServiceDescriptorPanel.jsx @@ -8,12 +8,13 @@ import React, {memo, useContext, useEffect, useState} from 'react'; import {getAppOptions} from '../../core/AppDataCntlr.js'; import {parseObsCoreRegion} from '../../util/ObsCoreSRegionParser.js'; import {computeCentralPtRadiusAverage} from '../../visualize/VisUtil.js'; +import {getStandardIdType, isSIAStandardID} from '../../voAnalyzer/VoCoreUtils'; import {FieldGroup, FieldGroupCtx} from '../FieldGroup.jsx'; import {getServiceMetaOptions, loadSiaV2Meta, makeObsCoreMetadataModel} from '../tap/SiaUtil'; import {CONTEXT_PARAMS_STR, convertRequest, DEFER_TO_CONTEXT, DynLayoutPanelTypes} from './DynamicUISearchPanel.jsx'; import {showInfoPopup} from '../PopupUtil.jsx'; import {isSimpleTargetPanel} from './DynComponents'; -import {getStandardIdType, hasAnySpacial, isSIAStandardID, sdToFieldDefAry} from './ServiceDefTools.js'; +import {hasAnySpacial, sdToFieldDefAry} from './ServiceDefTools.js'; import {ConstraintContext} from '../tap/Constraints'; import ShapeDataObj from '../../visualize/draw/ShapeDataObj.js'; @@ -26,8 +27,14 @@ import ShapeDataObj from '../../visualize/draw/ShapeDataObj.js'; * @prop {number} maxFetchDepth * @prop {String} HiPS * @prop {String} hips_initial_fov - * @prop {String} standardID + * @prop {String} hips_initial_dec + * @prop {String} hips_initial_dec + * @prop {boolean} ptIsGalactic * @prop {WorldPt} centerWp + * @prop {String} standardID + * @prop {Object} polygon_examples + * @prop {Object} examples + * @prop {Array.} mocList * @prop {CoordinateSys} coordinateSys */ diff --git a/src/firefly/js/visualize/HiPSMocUtil.js b/src/firefly/js/visualize/HiPSMocUtil.js index 128f50bbf1..f65b0e4766 100644 --- a/src/firefly/js/visualize/HiPSMocUtil.js +++ b/src/firefly/js/visualize/HiPSMocUtil.js @@ -1,4 +1,5 @@ import {get, set, isEmpty, flatten, isArray} from 'lodash'; +import {Style} from './draw/DrawingDef'; import {dispatchCreateDrawLayer} from './DrawLayerDispatch'; import {getDrawLayersByType} from './PlotViewUtil.js'; import HiPSMOC from '../drawingLayers/HiPSMOC.js'; @@ -17,6 +18,19 @@ export const MOCInfo = 'mocInfo'; export const UNIQCOL = 'uniqColName'; export const MOCOrder = 'mocOrder'; + +export const mocUIDisplayOptions= [ + {label: 'Outline', value: Style.DESTINATION_OUTLINE.key}, + {label: 'Fill', value: Style.FILL.key}, + {label: 'Auto', value: Style.AUTO.key}, + {label: 'MOC Tile Outline', value: Style.STANDARD.key}, +]; + +export function convertMocLabelToDisplayStyle(label) { + mocUIDisplayOptions.find( (op) => op.label.toLowerCase() === label?.toLowerCase())?.value; +} + + export function getAppHiPSForMoc() { return get(getAppOptions(), ['hips', 'hipsForMoc'], 'https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color'); } @@ -205,6 +219,7 @@ export function getMocOrderIndex(Nuniq) { * @param {Object} params moc table id * @param {string} params.tbl_id moc table id * @param {string} [params.title] optional shorter title + * @param {string} [params.mocStyle] The defualt display style of the MOC * @param {string} [params.shortTitle] optional title * @param {string} params.fitsPath moc fits path at the server after upload * @param {string} params.mocUrl moc fits url @@ -215,8 +230,8 @@ export function getMocOrderIndex(Nuniq) { * @param {string} [params.mocGroupDefColorId ] - group color id * @returns {T|SelectInfo|*|{}} */ -export function addNewMocLayer({tbl_id, title, shortTitle, fitsPath, mocUrl, uniqColName = 'NUNIQ', maxFetchDepth, - tablePreloaded=false, color, mocGroupDefColorId }) { +export function addNewMocLayer({tbl_id, title, mocStyle, shortTitle, 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); @@ -225,7 +240,8 @@ export function addNewMocLayer({tbl_id, title, shortTitle, fitsPath, mocUrl, uni if (title) title= 'MOC - ' + title; const mocFitsInfo = {fitsPath, mocUrl, uniqColName, tbl_id, tablePreloaded}; dl = dispatchCreateDrawLayer(HiPSMOC.TYPE_ID, - {mocFitsInfo,title,shortTitle,layersPanelLayoutId:'mocUIGroup', color, mocGroupDefColorId,maxFetchDepth}); + {mocFitsInfo,title,mocStyle,shortTitle,layersPanelLayoutId:'mocUIGroup', + color, mocGroupDefColorId,maxFetchDepth}); } return dl; } diff --git a/src/firefly/js/visualize/task/PlotHipsTask.js b/src/firefly/js/visualize/task/PlotHipsTask.js index 362428dc53..7b2c1a7422 100644 --- a/src/firefly/js/visualize/task/PlotHipsTask.js +++ b/src/firefly/js/visualize/task/PlotHipsTask.js @@ -263,12 +263,13 @@ async function makeHiPSPlot(rawAction, dispatcher) { -export function createHiPSMocLayerFromPreloadedTable({tbl_id,title, shortTitle, fitsPath, mocUrl, plotId, visible=false, - maxFetchDepth, color, mocGroupDefColorId, attachAllPlot=false} ) { +export function createHiPSMocLayerFromPreloadedTable({tbl_id,title, mocStyle, shortTitle, fitsPath, mocUrl, + plotId, visible=false, maxFetchDepth, color, + mocGroupDefColorId, attachAllPlot=false} ) { const table= getTblById(tbl_id); if (!table || table.isFetching) return; const uniqColName= table.tableData.columns[0].name; - const dl = addNewMocLayer({ tbl_id, title, shortTitle, fitsPath, mocUrl, uniqColName, + const dl = addNewMocLayer({ tbl_id, title, shortTitle, mocStyle, 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/TargetHiPSPanel.jsx b/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx index 5b91530c1b..90fc9df632 100644 --- a/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx +++ b/src/firefly/js/visualize/ui/TargetHiPSPanel.jsx @@ -473,7 +473,7 @@ function loadMocWithAbort(mocList, plotId,setMocError) { try { for(let i=0; (i standardID?.toLowerCase().startsWith(standardIDs.sia); +export const isSSAStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.ssa); +export const isSODAStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.soda); +export const isTAPStandardID = (standardID) => standardID?.toLowerCase().startsWith(standardIDs.tap); +export const isCisxTapStandardID = (standardID, utype) => + standardID.toLowerCase().startsWith(standardIDs.tap) && utype.toLowerCase() === cisxAdhocServiceUtype; + +export function getStandardIdType(standardID) { + if (isSIAStandardID(standardID)) return standardIDs.sia; + if (isSSAStandardID(standardID)) return standardIDs.ssa; + if (isSODAStandardID(standardID)) return standardIDs.soda; + if (isTAPStandardID(standardID)) return standardIDs.tap; +} /** * @see {@link http://www.ivoa.net/documents/VOTable/20130920/REC-VOTable-1.3-20130920.html#ToC54} @@ -93,7 +108,3 @@ export function getServiceSelfDescription(tableOrId) { return table.resources ?.filter( (r) => r.type==='meta' && (r.utype==='adhoc:this'||r.utype==='adhoc:service'))?.[0]; } - - - - diff --git a/src/firefly/js/voAnalyzer/VoDataLinkServDef.js b/src/firefly/js/voAnalyzer/VoDataLinkServDef.js index 4c16cc3d58..19882ea718 100644 --- a/src/firefly/js/voAnalyzer/VoDataLinkServDef.js +++ b/src/firefly/js/voAnalyzer/VoDataLinkServDef.js @@ -3,19 +3,18 @@ */ import {isArray} from 'lodash'; import {MetaConst} from '../data/MetaConst'; +import { + columnIDToName, getCellValue, getColumnByRef, getColumnIdx, getMetaEntry, getTblRowAsObj +} from '../tables/TableUtil.js'; import {isDefined} from '../util/WebUtil'; import {makeWorldPt, parseWorldPt} from '../visualize/Point'; import { - getObsCoreAccessFormat, getObsCoreAccessURL, getObsCoreProdType, getObsCoreSRegion, - isDatalinkTable, isObsCoreLike, + getObsCoreAccessFormat, getObsCoreAccessURL, getObsCoreProdType, getObsCoreSRegion, isDatalinkTable, isObsCoreLike, } from './TableAnalysis'; import { - adhocServiceUtype, cisxAdhocServiceUtype, standardIDs, VO_TABLE_CONTENT_TYPE, - DATALINK_COL_NAMES, RA_UCDs, DEC_UCDs, CLOUD_ACCESS, ipacMultiSpectrum, + adhocServiceUtype, cisxAdhocServiceUtype, CLOUD_ACCESS, DATALINK_COL_NAMES, DEC_UCDs, RA_UCDs, standardIDs, + VO_TABLE_CONTENT_TYPE, } from './VoConst.js'; -import { - columnIDToName, getCellValue, getColumnByRef, getColumnIdx, getMetaEntry, getTblRowAsObj -} from '../tables/TableUtil.js'; import {getTableModel} from './VoCoreUtils.js'; @@ -326,3 +325,33 @@ export function findWorldPtInServiceDef(serDef,sourceRow) { */ export const getStandardId= (serviceDef) => (serviceDef.standardID??'').toLowerCase(); export const getUtype= (serviceDef) => (serviceDef?.utype??'').toLowerCase(); + +//check for and return a datalink service descriptor url and input params, if one is found +export function checkForDatalinkServDesc(tblModel) { + const serviceDescriptors = getServiceDescriptors(tblModel); + if (!serviceDescriptors) return null; + + for (const sd of serviceDescriptors) { + //serviceDescriptors.forEach((sd) => { + const isDatalinkSerDesc = isDataLinkServiceDesc(sd); + if (isDatalinkSerDesc) { + const productUrl = { + accessURL: sd?.accessURL || '', + inputParams: {} + }; + + if (sd?.serDefParams) { + for (const param of sd.serDefParams) { + const {name,value,ref}= param ?? {}; + if (name) productUrl.inputParams[name] = {value, ref}; + } + } + + //return only when valid datalink access url is found + if (productUrl.accessURL || Object.keys(productUrl.inputParams).length > 0) { + return productUrl; + } + } + } + return null; //no valid datalink service descriptor found +} \ No newline at end of file