/* * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ import {flatten, flattenDeep, isArray, uniqueId, uniqBy, get, isEmpty} from 'lodash'; import {WebPlotRequest, GridOnStatus} from '../WebPlotRequest.js'; import ImagePlotCntlr, {visRoot, makeUniqueRequestKey, IMAGE_PLOT_KEY} from '../ImagePlotCntlr.js'; import {dlRoot, dispatchCreateDrawLayer, dispatchAttachLayerToPlot} from '../DrawLayerCntlr.js'; import {dispatchActiveTarget, getActiveTarget} from '../../core/AppDataCntlr.js'; import {WebPlot,PlotAttribute, RDConst, isImage} from '../WebPlot.js'; import CsysConverter from '../CsysConverter.js'; import VisUtils from '../VisUtil.js'; import {PlotState} from '../PlotState.js'; import Point, {makeImagePt} from '../Point.js'; import {WPConst, DEFAULT_THUMBNAIL_SIZE} from '../WebPlotRequest.js'; import {Band} from '../Band.js'; import {PlotPref} from '../PlotPref.js'; // import ActiveTarget from '../../drawingLayers/ActiveTarget.js'; import {clone} from '../../util/WebUtil.js'; import {makePostPlotTitle} from '../reducer/PlotTitle.js'; import {dispatchAddViewerItems, getMultiViewRoot, findViewerWithItemId, EXPANDED_MODE_RESERVED, IMAGE, DEFAULT_FITS_VIEWER_ID} from '../MultiViewCntlr.js'; import {getPlotViewById, getDrawLayerByType, getDrawLayersByType, getDrawLayerById, getPlotViewIdList} from '../PlotViewUtil.js'; import {enableMatchingRelatedData, enableRelatedDataLayer} from '../RelatedDataUtil.js'; import {modifyRequestForWcsMatch} from './WcsMatchTask.js'; import WebGrid from '../../drawingLayers/WebGrid.js'; import HiPSGrid from '../../drawingLayers/HiPSGrid.js'; import {getDlAry} from '../DrawLayerCntlr.js'; import HiPSMOC from '../../drawingLayers/HiPSMOC.js'; import {dispatchPlotProgressUpdate, dispatchRecenter, dispatchWcsMatch} from '../ImagePlotCntlr'; import {isDefined} from '../../util/WebUtil'; import {HdrConst} from '../FitsHeaderUtil.js'; import {doFetchTable} from '../../tables/TableUtil'; import ImageRoot from '../../drawingLayers/ImageRoot'; //======================================== Exported Functions ============================= //======================================== Exported Functions ============================= export function ensureWPR(inVal) { if (isArray(inVal)) { return inVal.map( (v) => WebPlotRequest.makeFromObj(v)); } else { return WebPlotRequest.makeFromObj(inVal); } } export function determineViewerId(viewerId, plotId) { if (viewerId) return viewerId; const newViewerId= findViewerWithItemId(getMultiViewRoot(), plotId, IMAGE); return newViewerId || DEFAULT_FITS_VIEWER_ID; } export function getHipsImageConversion(hipsImageConversion ) { return hipsImageConversion && { hipsRequestRoot:ensureWPR(hipsImageConversion.hipsRequestRoot), imageRequestRoot:ensureWPR(hipsImageConversion.imageRequestRoot), allSkyRequest: ensureWPR(hipsImageConversion.allSkyRequest), fovDegFallOver: hipsImageConversion.fovDegFallOver, fovMaxFitsSize: hipsImageConversion.fovMaxFitsSize, plotAllSkyFirst: hipsImageConversion.plotAllSkyFirst, autoConvertOnZoom: hipsImageConversion.autoConvertOnZoom }; } const getFirstReq= (wpRAry) => isArray(wpRAry) ? wpRAry.find( (r) => Boolean(r)) : wpRAry; function makeSinglePlotPayload(vr, rawPayload, requestKey) { const {threeColor, attributes, setNewPlotAsActive= true, holdWcsMatch= true, useContextModifications= true, enableRestore= true, renderTreeId}= rawPayload; let {plotId, wpRequest, pvOptions= {}}= rawPayload; wpRequest= ensureWPR(wpRequest); const hipsImageConversion= getHipsImageConversion(rawPayload.hipsImageConversion); if (hipsImageConversion) pvOptions= clone(pvOptions, {hipsImageConversion}); const req= getFirstReq(wpRequest); if (isArray(wpRequest)) { if (!plotId) plotId= req.getPlotId() || uniqueId('defaultPlotId-'); wpRequest.forEach( (r) => {if (r) r.setPlotId(plotId);}); } else { if (!plotId) plotId= req.getPlotId() || uniqueId('defaultPlotId-'); wpRequest.setPlotId(plotId); } if (vr.wcsMatchType && vr.mpwWcsPrimId && holdWcsMatch) { const wcsPrim= getPlotViewById(vr,vr.mpwWcsPrimId); wpRequest= isArray(wpRequest) ? wpRequest.map( (r) => modifyRequestForWcsMatch(wcsPrim, r)) : modifyRequestForWcsMatch(wcsPrim, wpRequest); } const payload= { plotId: req.getPlotId(), plotGroupId: req.getPlotGroupId(), groupLocked: req.isGroupLocked(), viewerId: determineViewerId(rawPayload.viewerId, plotId), hipsImageConversion, requestKey, attributes, pvOptions, enableRestore, useContextModifications, threeColor, setNewPlotAsActive, renderTreeId}; const existingPv= getPlotViewById(vr,plotId); if (existingPv) { payload.oldOverlayPlotViews= {[plotId] :existingPv.overlayPlotViews}; } if (threeColor) { if (isArray(wpRequest)) { payload.redReq= addRequestKey(wpRequest[Band.RED.value], requestKey); payload.greenReq= addRequestKey(wpRequest[Band.GREEN.value], requestKey); payload.blueReq= addRequestKey(wpRequest[Band.BLUE.value], requestKey); } else { payload.redReq= addRequestKey(wpRequest,requestKey); } } else { payload.wpRequest= addRequestKey(wpRequest,requestKey); } return payload; } /** * * @param rawAction * @return {Function} */ export function makePlotImageAction(rawAction) { return (dispatcher, getState) => { let vr= getState()[IMAGE_PLOT_KEY]; const {wpRequestAry}= rawAction.payload; let payload; const requestKey= makeUniqueRequestKey('plotRequestKey'); if (!wpRequestAry) { payload= makeSinglePlotPayload(vr, rawAction.payload, requestKey); } else { const {viewerId=DEFAULT_FITS_VIEWER_ID, attributes, setNewPlotAsActive= true, pvOptions= {}, useContextModifications= true, enableRestore= true, renderTreeId}= rawAction.payload; payload= { wpRequestAry:ensureWPR(wpRequestAry), viewerId, attributes, pvOptions, setNewPlotAsActive, threeColor:false, useContextModifications, enableRestore, groupLocked:true, requestKey, renderTreeId }; payload.wpRequestAry= payload.wpRequestAry.map( (req) => addRequestKey(req,makeUniqueRequestKey('groupItemReqKey-'+req.getPlotId()))); payload.oldOverlayPlotViews= wpRequestAry .map( (wpr) => getPlotViewById(vr,wpr.getPlotId())) .filter( (pv) => get(pv, 'overlayPlotViews')) .reduce( (obj, pv) => { obj[pv.plotId]= pv.overlayPlotViews; return obj; },{}); if (vr.wcsMatchType && vr.mpwWcsPrimId && rawAction.payload.holdWcsMatch) { const wcsPrim= getPlotViewById(vr,vr.mpwWcsPrimId); payload.wpRequestAry= payload.wpRequestAry.map( (wpr) => modifyRequestForWcsMatch(wcsPrim, wpr)); } } // if (!getDrawLayerByType(getDlAry(), ActiveTarget.TYPE_ID)) { // initBuildInDrawLayers(); // } payload.requestKey= requestKey; payload.plotType= 'image'; vr= getState()[IMAGE_PLOT_KEY]; if (vr.wcsMatchType && !rawAction.payload.holdWcsMatch) { dispatcher({ type: ImagePlotCntlr.WCS_MATCH, payload: {wcsMatchType:false} }); } dispatcher( { type: ImagePlotCntlr.PLOT_IMAGE_START,payload}); // NOTE - saga ImagePlotter handles next step // NOTE - saga ImagePlotter handles next step // NOTE - saga ImagePlotter handles next step }; } function addRequestKey(r,requestKey) { if (!r) return; r= r.makeCopy(); r.setRequestKey(requestKey); return r; } //======================================== Private ====================================== //======================================== Private ====================================== //======================================== Private ====================================== /** * * @param {object} pvCtx * @param {WebPlotRequest} r * @param {Band} band * @param useCtxMods * @return {WebPlotRequest} */ export function modifyRequest(pvCtx, r, band, useCtxMods) { if (!r) return r; const retval= r.makeCopy(); if (retval.getRotateNorth()) retval.setRotateNorth(false); if (retval.getRotate()) retval.setRotate(false); if (retval.getRotationAngle()) retval.setRotationAngle(0); if (!pvCtx || !useCtxMods) return retval; if (pvCtx.defThumbnailSize!==DEFAULT_THUMBNAIL_SIZE && !r.containsParam(WPConst.THUMBNAIL_SIZE)) { retval.setThumbnailSize(pvCtx.defThumbnailSize); } const cPref= PlotPref.getCacheColorPref(pvCtx.preferenceColorKey); if (cPref) { if (cPref[band]) retval.setInitialRangeValues(cPref[band]); retval.setInitialColorTable(cPref.colorTableId); } const zPref= PlotPref.getCacheZoomPref(pvCtx.preferenceZoomKey); if (zPref) { retval.setInitialZoomLevel(zPref.zooomLevel); } return retval; } /** * * @param dispatcher * @param {object} payload the payload of the original action * @param {object} result the result of the search */ /** * * @param dispatcher * @param {object} payload the payload of the original action * @param {object} result the result of the search */ export function processPlotImageSuccessResponse(dispatcher, payload, result) { let successAry= []; let failAry= []; // the following line checks to see if we are processing the results from the right request if (payload.requestKey && result.requestKey && payload.requestKey!==result.requestKey) return; if (result.success && Array.isArray(result.data)) { successAry= result.data.filter( (d) => d.data.success); failAry= result.data.filter( (d) => !d.data.success); } else { if (result.success) successAry= [{data:result}]; else failAry= [{data:result}]; } successAry.forEach( (r) => { const plotState= PlotState.makePlotStateWithJson(r.data.PlotCreate[0].plotState); const wpRequest= r.data.PlotCreateHeader ? WebPlotRequest.parse(r.data.PlotCreateHeader.plotRequestSerialize) : plotState.getWebPlotRequest(); dispatchPlotProgressUpdate(wpRequest.getPlotId(), 'Loading Images', false,wpRequest.getRequestKey()); }); lookForRelatedDataThenContinue(successAry,failAry, payload, dispatcher); } function lookForRelatedDataThenContinue(successAry,failAry, payload, dispatcher) { setTimeout( () => { const promiseAry= [Promise.resolve()]; successAry.forEach( (s) => s.data.PlotCreate.forEach( (pc) => { const tType= pc.relatedData && pc.relatedData.find( (r) => r.dataType==='WAVELENGTH_TABLE'); if (tType) { const p= doFetchTable(tType.searchParams).then( (wlTable) => { pc.relatedData.push({dataType:'WAVELENGTH_TABLE_RESOLVED',dataKey:tType.dataKey+'-resolved', table:wlTable}); }); promiseAry.push(p); } })); Promise.all(promiseAry).then( () =>continuePlotImageSuccess(dispatcher, payload, successAry, failAry)); } , 5); } function continuePlotImageSuccess(dispatcher, payload, successAry, failAry) { if (successAry.length) { const pvNewPlotInfoAry= successAry.map( (r) => handleSuccessfulCall(r.data.PlotCreate, r.data.PlotCreateHeader,payload, r.data.requestKey) ); const resultPayload= Object.assign({},payload, {pvNewPlotInfoAry}); dispatcher({type: ImagePlotCntlr.PLOT_IMAGE, payload: resultPayload}); const plotIdAry = pvNewPlotInfoAry.map((info) => info.plotId); dispatcher({type: ImagePlotCntlr.ANY_REPLOT, payload: {plotIdAry}}); matchAndActivateOverlayPlotViewsByGroup(plotIdAry); pvNewPlotInfoAry .forEach((info) => info.plotAry .forEach( (p) => { const pv= getPlotViewById(visRoot(),p.plotId); addDrawLayers(p.plotState.getWebPlotRequest(), pv, p); if (p.attributes[PlotAttribute.INIT_CENTER]) dispatchRecenter({plotId:p.plotId}); } )); //todo- this this plot is in a group and locked, make a unique list of all the drawing layers in the group and add to new dispatchAddViewerItems(EXPANDED_MODE_RESERVED, plotIdAry, IMAGE); const vr= visRoot(); if (vr.wcsMatchType && vr.positionLock) { dispatchWcsMatch( {plotId:vr.activePlotId, matchType:vr.wcsMatchType, lockMatch:true}); } } failAry.forEach( (r) => { const {data}= r; if (payload.plotId) dispatchAddViewerItems(EXPANDED_MODE_RESERVED, [payload.plotId], IMAGE); const failPayload= { ...payload, briefDescription: data.briefFailReason, description: 'Failed- ' + data.userFailReason, detailFailReason: data.detailFailReason, plotId: data.plotId, }; dispatcher( { type: ImagePlotCntlr.PLOT_IMAGE_FAIL, payload:failPayload} ); }); } export function addDrawLayers(request, pv, plot) { const {plotId}= plot; const fixedLayers= getDrawLayersByType(getDlAry(), ImageRoot.TYPE_ID); let newDL= fixedLayers.find( (dl) => dl.plotId===plot.plotId); if (!newDL) { newDL= dispatchCreateDrawLayer(ImageRoot.TYPE_ID, {plotId}); dispatchAttachLayerToPlot(newDL.drawLayerId, pv.plotId, false); } request.getOverlayIds().forEach((drawLayerTypeId)=> { const dls = getDrawLayersByType(dlRoot(), drawLayerTypeId); dls.forEach((dl) => { if (dl.canAttachNewPlot) { const visibility = (dl.drawLayerTypeId === HiPSGrid.TYPE_ID) || (dl.drawLayerTypeId === HiPSMOC.TYPE_ID && isEmpty(dl.visiblePlotIdAry)) ? false : true; dispatchAttachLayerToPlot(dl.drawLayerId, plotId, true, visibility, false); } }); }); if (request.getGridOn()!==GridOnStatus.FALSE && isImage(plot)) { const dl = getDrawLayerByType(dlRoot(), WebGrid.TYPE_ID); const useLabels= request.getGridOn()===GridOnStatus.TRUE; if (!dl) dispatchCreateDrawLayer(WebGrid.TYPE_ID, {useLabels}); dispatchAttachLayerToPlot(WebGrid.TYPE_ID, plotId, false); } if (plot.relatedData) { plot.relatedData.forEach( (rd) => { if (rd.dataType === RDConst.TABLE) { const dl = getDrawLayerById(dlRoot(), rd.relatedDataId); if (!dl) enableRelatedDataLayer(visRoot(), getPlotViewById(visRoot(), plotId), rd); } }); } } // function getRequest(payload) { // return payload.wpRequest || payload.redReq || payload.blueReq || payload.greenReq; // } /** * @global * @public * @typedef {Object} PvNewPlotInfo * @summary Main part of the payload of successful call to the server * * @prop {String} plotId, * @prop {String} requestKey, * @prop {WebPlot[]} plotAry * @prop {OverPlotView[]} overlayPlotViews * */ function findCubePlane(plotCreate) { const plotState= PlotState.makePlotStateWithJson(plotCreate.plotState); if (plotState.isThreeColor()) return -1; if (plotCreate.headerAry && isDefined(plotCreate.headerAry[0][HdrConst.SPOT_PL])) { // this should be the zero plane of the cube return Number(plotCreate.headerAry[0][HdrConst.SPOT_PL].value); } else if (!plotCreate.headerAry) { // if no headerAry, it is a plane of the cube return plotState.getCubePlaneNumber(); } else { return -1; } } function populateBandStateFromHeader(bandState, plotCreateHeader) { bandState.plotRequestSerialize = plotCreateHeader.plotRequestSerialize; bandState.uploadFileNameStr= plotCreateHeader.uploadFileNameStr; bandState.originalFitsFileStr= plotCreateHeader.originalFitsFileStr; bandState.workingFitsFileStr= plotCreateHeader.workingFitsFileStr; } /** * readds the data into each plotCreate from the plotCreateHeader. * The data in the header is replicated in each plot and was clear for network transfere * efficiency. This optimization is very import for large cubes. * Note- data is modified in place plotCreate will me changed, no new object is created. * @param plotCreateHeader * @param plotCreate */ export function populateFromHeader(plotCreateHeader, plotCreate) { if (!plotCreateHeader) return; for(let i=0; i<plotCreate.length; i++) { if (isArray(plotCreate[0].bandStateAry)) { for (let j = 0; j < 3; j++) { if (plotCreate[0].bandStateAry[j]) { populateBandStateFromHeader(plotCreate[i].plotState.bandStateAry[j],plotCreateHeader); } } } else { populateBandStateFromHeader(plotCreate[i].plotState.bandStateAry,plotCreateHeader); } plotCreate[i].dataDesc= plotCreateHeader.dataDesc; plotCreate[i].zeroHeaderAry= plotCreateHeader.zeroHeaderAry; } } /** * * @param {Array.<Object>} plotCreate * @param {Object} plotCreateHeader * @param payload * @param requestKey * @return {PvNewPlotInfo} */ function handleSuccessfulCall(plotCreate, plotCreateHeader, payload, requestKey) { // const plotCreate= plotCreateStrAry.map( (s) => JSON.parse(s)); populateFromHeader(plotCreateHeader, plotCreate); let cubeStartIdx=-1; const cubeCtxAry= plotCreate .map( (pC,idx) => { const cubePlane= findCubePlane(pC); if (cubePlane===0) cubeStartIdx= idx; const cubeStartPC= plotCreate[cubeStartIdx]; return cubePlane>-1 ? { cubePlane, cubeHeaderAry: cubeStartPC.headerAry, relatedData: cubeStartPC.relatedData, dataWidth: cubeStartPC.dataWidth, dataHeight: cubeStartPC.dataHeight, imageCoordSys: cubeStartPC.imageCoordSys } : undefined; }); const plotState= PlotState.makePlotStateWithJson(plotCreate[0].plotState); const plotId= plotState.getWebPlotRequest().getPlotId(); const plotAry= plotCreate.map((wpInit,idx) => makePlot(wpInit,plotId, payload.attributes, cubeCtxAry[idx]) ); if (plotAry.length) updateActiveTarget(plotAry[0]); return {plotId, requestKey, plotAry, overlayPlotViews:null}; } function makePlot(wpInit,plotId, attributes, cubeCtx) { const plot= WebPlot.makeWebPlotData(plotId, wpInit, {}, false, cubeCtx); const r= plot.plotState.getWebPlotRequest(); plot.title= makePostPlotTitle(plot,r); if (r.isMinimalReadout()) plot.attributes[PlotAttribute.MINIMAL_READOUT]= true; if (r.getRelatedTableRow()>-1) plot.attributes[PlotAttribute.TABLE_ROW]= r.getRelatedTableRow(); if (r.getRelatedTableId()) plot.attributes[PlotAttribute.TABLE_ID]= r.getRelatedTableId(); Object.assign(plot.attributes,attributes); return plot; } /** * @param {WebPlot} plot */ function updateActiveTarget(plot) { if (!plot) return; const req= plot.plotState.getWebPlotRequest(); if (!req) return; let corners; let activeTarget; if (!getActiveTarget()) { const circle = req.getRequestArea(); if (req.getOverlayPosition()) activeTarget= req.getOverlayPosition(); else if (circle && circle.center) activeTarget= circle.center; else activeTarget= VisUtils.getCenterPtOfPlot(plot); } if (req.getSaveCorners()) { const w= plot.dataWidth; const h= plot.dataHeight; const cc= CsysConverter.make(plot); const pt1= cc.getWorldCoords(makeImagePt(0, 0)); const pt2= cc.getWorldCoords(makeImagePt(w, 0)); const pt3= cc.getWorldCoords(makeImagePt(w,h)); const pt4= cc.getWorldCoords(makeImagePt(0, h)); if (pt1 && pt2 && pt3 && pt4) { corners= [pt1,pt2,pt3,pt4]; } } if (activeTarget || corners) dispatchActiveTarget(activeTarget,corners); } // export function initBuildInDrawLayers() { // dispatchCreateDrawLayer(ActiveTarget.TYPE_ID); // } /** * * @param {String[]} plotIdAry * @param {Object.<string, OverlayPlotView[]>} oldOverlayPlotViews */ function matchAndActivateOverlayPlotViews(plotIdAry, oldOverlayPlotViews) { // plotIdAry.forEach( (plotId) => dispatchDeleteOverlayPlot({plotId, deleteAll:true})); plotIdAry .map( (plotId) => getPlotViewById(visRoot(), plotId)) .filter( (pv) => pv) .forEach( (pv) => enableMatchingRelatedData(pv,oldOverlayPlotViews[pv.plotId])); } /** * * @param {String[]} plotIdAry */ function matchAndActivateOverlayPlotViewsByGroup(plotIdAry) { const vr= visRoot(); plotIdAry .map( (plotId) => getPlotViewById(visRoot(), plotId)) .filter( (pv) => pv) .forEach( (pv) => { const opvMatchArray= uniqBy(flatten(getPlotViewIdList(vr, pv.plotId) .filter( (id) => id!== pv.plotId) .map( (id) => getPlotViewById(vr,id)) .map( (gpv) => gpv.overlayPlotViews)), 'maskNumber' ); enableMatchingRelatedData(pv,opvMatchArray); }); }