Source: visualize/WebPlotRequest.js

/**
 * User: roby
 * Date: Apr 2, 2009
 * Time: 9:18:47 AM
 */
/* eslint prefer-template:0 */
import {get, isString, isPlainObject, isArray, join} from 'lodash';
import {ServerRequest, ID_NOT_DEFINED} from '../data/ServerRequest.js';
import {RequestType} from './RequestType.js';
import {ZoomType} from './ZoomType.js';
import Enum from 'enum';
import CoordinateSys from './CoordSys.js';
import Point, {parseImagePt} from './Point.js';
import {parseResolver} from '../astro/net/Resolver.js';
import {RangeValues} from './RangeValues.js';




const DEFAULT_IMAGE_OVERLAYS= ['ACTIVE_TARGET_TYPE','POINT_SELECTION_TYPE', 'NORTH_UP_COMPASS_TYPE',
    'WEB_GRID_TYPE', 'OVERLAY_MARKER_TYPE', 'OVERLAY_FOOTPRINT_TYPE', 'REGION_PLOT_TYPE',
    'HIPS_GRID_TYPE', 'MOC_PLOT_TYPE'];

const DEFAULT_HIPS_OVERLAYS= ['ACTIVE_TARGET_TYPE','POINT_SELECTION_TYPE', 'NORTH_UP_COMPASS_TYPE',
    'OVERLAY_MARKER_TYPE', 'OVERLAY_FOOTPRINT_TYPE', 'REGION_PLOT_TYPE', 'HIPS_GRID_TYPE', 'MOC_PLOT_TYPE'];


/**
 * @summary service type
 * @description can be 'IRIS', 'ISSA', 'DSS', 'SDSS', 'TWOMASS', 'MSX', 'DSS_OR_IRIS', 'WISE', 'ATLAS','ZTF', 'PTF', 'NONE'
 * @public
 * @global
 */
export const ServiceType= new Enum(['IRIS', 'ISSA', 'DSS', 'SDSS', 'TWOMASS', 'MSX', 'DSS_OR_IRIS', 'WISE', 'ATLAS', 'ZTF', 'PTF', 'NONE'],
                                              { ignoreCase: true });
/**
 * @summary title options
 * @description can be 'NONE', 'PLOT_DESC', 'FILE_NAME', 'HEADER_KEY', 'PLOT_DESC_PLUS', 'SERVICE_OBS_DATE'
 * @public
 * @global
 */
export const TitleOptions= new Enum([
    'NONE',  // use what it in the title
    'PLOT_DESC', // use the plot description key
    'FILE_NAME', // use the file name or analyze the URL and make a title from that
    'HEADER_KEY', // use the header value
    'PLOT_DESC_PLUS', // ??
    'SERVICE_OBS_DATE'
], { ignoreCase: true });

export const AnnotationOps= new Enum([
    'INLINE',    //default inline title full title and tools
    'INLINE_BRIEF',  // inline brief title, no tools
    'INLINE_BRIEF_TOOLS', // brief title w/ tools
    'TITLE_BAR', // title full title and tools
    'TITLE_BAR_BRIEF', // title bar brief title, no tools
    'TITLE_BAR_BRIEF_TOOLS', // title bar brief with w/ tools
    'TITLE_BAR_BRIEF_CHECK_BOX' // title bar brief title with a check box (used in planck)
], { ignoreCase: true });



export const ExpandedTitleOptions= new Enum([ 'REPLACE',// use expanded title when expanded
                                     'PREFIX',// use expanded title as prefix to title
                                     'SUFFIX'// use expanded title as suffix to title
                                   ], { ignoreCase: true });
export const GridOnStatus= new Enum(['FALSE','TRUE','TRUE_LABELS_FALSE'], { ignoreCase: true });

export const DEFAULT_THUMBNAIL_SIZE= 70;
const WEB_PLOT_REQUEST_CLASS= 'WebPlotRequest';
const Order= new Enum(['FLIP_Y', 'FLIP_X', 'ROTATE', 'POST_CROP', 'POST_CROP_AND_CENTER'], { ignoreCase: true });


const C= {
    FILE : 'File',
    WORLD_PT : 'WorldPt',
    URLKEY : 'URL',
    SIZE_IN_DEG : 'SizeInDeg',
    SURVEY_KEY : 'SurveyKey',
    SURVEY_KEY_ALT : 'SurveyKeyAlt',
    SURVEY_KEY_BAND : 'SurveyKeyBand',
    TYPE : 'Type',
    ZOOM_TYPE : 'ZoomType',
    SERVICE : 'Service',
    USER_DESC : 'UserDesc',
    INIT_ZOOM_LEVEL : 'InitZoomLevel',
    TITLE : 'Title',
    ROTATE_NORTH : 'RotateNorth',
    ROTATE_NORTH_TYPE : 'RotateNorthType',
    ROTATE : 'Rotate',
    ROTATE_FROM_NORTH : 'RotateFromNorth',
    ROTATION_ANGLE : 'RotationAngle',
    HEADER_KEY_FOR_TITLE : 'HeaderKeyForTitle',
    INIT_RANGE_VALUES : 'RangeValues',
    INIT_COLOR_TABLE : 'ColorTable',
    MULTI_IMAGE_IDX : 'MultiImageIdx',
    MULTI_IMAGE_EXTS: 'MultiImageExts',
    ZOOM_TO_WIDTH : 'ZoomToWidth',
    ZOOM_TO_HEIGHT : 'ZoomToHeight',
    ZOOM_ARCSEC_PER_SCREEN_PIX : 'ZoomArcsecPerScreenPix',
    POST_CROP : 'PostCrop',
    POST_CROP_AND_CENTER : 'PostCropAndCenter',
    POST_CROP_AND_CENTER_TYPE : 'PostCropAndCenterType',
    CROP_PT1 : 'CropPt1',
    CROP_PT2 : 'CropPt2',
    CROP_WORLD_PT1 : 'CropWorldPt1',
    CROP_WORLD_PT2 : 'CropWorldPt2',
    UNIQUE_KEY : 'UniqueKey',
    CONTINUE_ON_FAIL : 'ContinueOnFail',
    OBJECT_NAME : 'ObjectName',
    RESOLVER : 'Resolver',
    PLOT_DESC_APPEND : 'PlotDescAppend',
    BLANK_ARCSEC_PER_PIX : 'BlankArcsecPerScreenPix',  //todo: doc
    BLANK_PLOT_WIDTH : 'BlankPlotWidth',               //todo: doc
    BLANK_PLOT_HEIGHT : 'BlankPlotHeight',             //todo: doc
    PROGRESS_KEY : 'ProgressKey',
    FLIP_X : 'FlipX',
    FLIP_Y : 'FlipY',
    HAS_MAX_ZOOM_LEVEL : 'HasMaxZoomLevel',
    THUMBNAIL_SIZE : 'thumbnailSize',
    PIPELINE_ORDER : 'pipelineOrder',
    URL_CHECK_FOR_NEWER: 'urlCheckForNewer',

    MULTI_PLOT_KEY: 'MultiPlotKey',
    THREE_COLOR_PLOT_KEY: 'ThreeColorPlotKey',
    THREE_COLOR_HINT: 'ThreeColorHint',
    RED_HINT: 'RedHint',
    GREEN_HINT: 'GreenHint',
    BLUE_HINT: 'BlueHint',

// keys - client side operations
// note- if you add a new key make sure you put it in the _allKeys array
    PLOT_TO_DIV : 'PlotToDiv',
    PREFERENCE_COLOR_KEY : 'PreferenceColorKey',
    PREFERENCE_ZOOM_KEY : 'PreferenceZoomKey',
    ROTATE_NORTH_SUGGESTION : 'RotateNorthSuggestion',
    SAVE_CORNERS : 'SaveCornersAfterPlot',
    SHOW_SCROLL_BARS : 'showScrollBars',  // todo deprecate
    ALLOW_IMAGE_SELECTION : 'AllowImageSelection',
    HAS_NEW_PLOT_CONTAINER : 'HasNewPlotContainer',
    GRID_ON : 'GridOn',
    OVERLAY_POSITION : 'OverlayPosition',
    INITIAL_CENTER_POSITION : 'InitialCenterPosition',
    MINIMAL_READOUT: 'MinimalReadout',
    PLOT_GROUP_ID: 'plotGroupId',
    GROUP_LOCKED: 'GroupLocked',
    DRAWING_SUB_GROUP_ID: 'DrawingSubgroupID',
    //GRID_ID : 'GRID_ID', - deprecated
    DOWNLOAD_FILENAME_ROOT : 'DownloadFileNameRoot',
    PLOT_ID : 'plotId',
    OVERLAY_IDS: 'PredefinedOverlayIds',
    RELATED_TABLE_ROW : 'RELATED_TABLE_ROW',
    RELATED_TABLE_ID : 'RELATED_TABLE_ID',
    HIPS_ROOT_URL: 'hipsRootUrl',
    HIPS_SURVEYS_ID: 'hipsSurveysId',



    //SHOW_TITLE_AREA : 'ShowTitleArea',  // deprecate
    //HIDE_TITLE_DETAIL : 'HideTitleDetail',// deprecate
    //TITLE_FILENAME_MODE_PFX : 'TitleFilenameModePfx', // deprecate
    ANNOTATION_OPS : 'AnnotationOps',
    TITLE_OPTIONS : 'TitleOptions',
    EXPANDED_TITLE_OPTIONS : 'ExpandedTitleOptions',
    EXPANDED_TITLE : 'ExpandedTitle',
    POST_TITLE: 'PostTitle',
    PRE_TITLE: 'PreTitle',

    MASK_BITS: 'MaskBits',
    PLOT_AS_MASK: 'PlotAsMask',
    MASK_COLORS: 'MaskColors',
    MASK_REQUIRED_WIDTH: 'MaskRequiredWidth',
    MASK_REQUIRED_HEIGHT: 'MaskRequiredHeight'

};

const allKeys= new Enum(Object.keys(C).map( (k) => C[k]), { ignoreCase: true });


const clientSideKeys =
        [C.UNIQUE_KEY,
         C.PLOT_TO_DIV, C.PREFERENCE_COLOR_KEY, C.PREFERENCE_ZOOM_KEY,
         C.ROTATE_NORTH_SUGGESTION, C.SAVE_CORNERS,
         C.SHOW_SCROLL_BARS, C.EXPANDED_TITLE,
         C.ALLOW_IMAGE_SELECTION, C.HAS_NEW_PLOT_CONTAINER,
         C.GRID_ON,
         C.TITLE_OPTIONS, C.EXPANDED_TITLE_OPTIONS,
         C.POST_TITLE, C.PRE_TITLE, C.OVERLAY_POSITION,
         C.MINIMAL_READOUT,
         C.PLOT_GROUP_ID, C.DRAWING_SUB_GROUP_ID,  C.RELATED_TABLE_ROW,
         C.DOWNLOAD_FILENAME_ROOT, C.PLOT_ID, C.GROUP_LOCKED,
         C.OVERLAY_IDS, C.INITIAL_CENTER_POSITION
        ];

const ignoreForEquals = [C.PROGRESS_KEY, C.ZOOM_TO_WIDTH, C.ZOOM_TO_HEIGHT,
                          C.ZOOM_TYPE, C.HAS_NEW_PLOT_CONTAINER];

const DEFAULT_PIPELINE_ORDER= Order.ROTATE.key+';'+
                              Order.FLIP_Y.key+';'+
                              Order.FLIP_X.key+';'+
                              Order.POST_CROP.key+';'+
                              Order.POST_CROP_AND_CENTER.key;

const makeOrderList = (orderStr) => orderStr ? orderStr.split(';').map( (v) => Order.get(v)) : [];

const DEF_ORDER= makeOrderList(DEFAULT_PIPELINE_ORDER);

/**
 * @summary Web plot request. This object can be created by using the method of making survey request like *makexxxRequest*
 * and the method of setting the parameters.
 * @param {RequestType} type request type
 * @param {string} userDesc description
 * @param {ServiceType} serviceType service type if type == RequestType.SERVICE
 *
 * @public
 * @global
 */
export class WebPlotRequest extends ServerRequest {
    constructor(type,userDesc,serviceType) {
        super(type);
        if (type) this.setRequestType(type);

        this.setRequestClass(WEB_PLOT_REQUEST_CLASS);

        if (serviceType && serviceType!==ServiceType.NONE) this.setServiceType(serviceType);
        if (userDesc) this.setParam(C.USER_DESC, userDesc);
    }


//======================================================================
//----------- Factory Methods for various types of request    ----------
//----------- Most of the time it is better to use these than ----------
//----------- the constructors                                ----------
//======================================================================
    
    static makeFromObj(obj) {
        if (!obj) return null;
        if (isString(obj)) {
            return WebPlotRequest.parse(obj);
        }
        else if (isPlainObject(obj)) {
            const wpr= new WebPlotRequest();
            wpr.setParams(cleanupObj(obj));


            let typeGuess;
            if (obj.id) typeGuess= RequestType.PROCESSOR;
            else if (obj[C.FILE]) typeGuess= RequestType.FILE;
            else if (obj[C.URLKEY]) typeGuess= RequestType.URL;
            else if (obj[C.SURVEY_KEY]) typeGuess= RequestType.SERVICE;
            else if (obj[C.HIPS_ROOT_URL]) typeGuess= RequestType.HiPS;

            if (typeGuess && !wpr.params[C.TYPE]) wpr.setRequestType(typeGuess);

            // setting safe urls
            if (wpr.params[C.URLKEY]) wpr.setURL(wpr.params[C.URLKEY]);
            if (wpr.params[C.HIPS_ROOT_URL]) wpr.setHipsRootUrl(wpr.params[C.HIPS_ROOT_URL]);
            

            return wpr;
        }
        else if (obj.makeCopy) { // in this case I was probably passed a WebPlotRequest
           return obj.makeCopy();
        }
        else { // i don't know what I have, just return it
            return obj;
        }
    }

    /**
     * Makes a WebPlotRequest from another request.
     * There are two things to note: The ServerRequest.ID_KEY is not set.
     * If this is a RequestType.PROCESSOR, then you should do the follow:
     * <ul>
     * <li>call setRequestType(RequestType.PROCESSOR)</li>
     * <li>call setParam(ServerRequest.ID_KEY, <i>someID</i>)</li>
     * </ul>
     * or if you can create the WebPlotRequest by calling makeProcessorRequest instead
     *
     * @param serverReq the request
     * @return {ServerRequest} the new WebPlotRequest
     */
    makeRequest(serverReq) {
        let retval;
        if (serverReq instanceof WebPlotRequest) {
            retval= serverReq;
        }
        else {
            retval = new WebPlotRequest(RequestType.FILE, 'Fits File');
            retval.setParams(serverReq.getParams());
            retval.removeParam(this.ID_KEY);
        }
        return retval;
    }



    static makeFilePlotRequest(fileName, initZoomLevel) {
        const req = new WebPlotRequest(RequestType.FILE, 'Fits file: ' + fileName);
        req.setParam(C.FILE, fileName);
        req.setTitleOptions(TitleOptions.FILE_NAME);
        if (initZoomLevel) {
            req.setParam(C.INIT_ZOOM_LEVEL, initZoomLevel + '');
        }
        else {
            req.setParam(C.INIT_ZOOM_LEVEL, 1);
            // req.setZoomType(ZoomType.TO_WIDTH); //todo fix when we can auto zoom to with: dm-4759
        }
        return req;
    }

    static makeProcessorRequest(serverRequest, desc) {
        const req = new WebPlotRequest(RequestType.PROCESSOR, desc);
        req.setParams(serverRequest.getParams());
        return req;
    }

    static makeURLPlotRequest(url, userDesc) {
        const req = new WebPlotRequest(RequestType.URL, userDesc||'Fits from URL: ' + url);
        req.setTitleOptions(TitleOptions.FILE_NAME);
        req.setURL(url);
        return req;
    }

    static makeWorkspaceRequest(filePath, userDesc) {
        const req = new WebPlotRequest(RequestType.WORKSPACE, userDesc||filePath);
        req.setTitleOptions(TitleOptions.FILE_NAME);
        req.setFileName(filePath);
        return req;
    };

    static makeTblFilePlotRequest(fileName) {
        const req = new WebPlotRequest(RequestType.FILE, 'Table: ' + fileName);
        req.setParam(C.FILE, fileName);
        return req;
    }


    static makeTblURLPlotRequest(url) {
        const req = new WebPlotRequest(RequestType.URL, 'Table from URL: ' + url);
        req.setParam(C.URLKEY, url);
        return req;
    }

    //======================== ISSA =====================================


    /**
     *
     * @param worldPt
     * @param {string} survey  must be one of '12','25','60','100'
     * @param sizeInDeg less then 12.5
     * @return {WebPlotRequest}
     */
    static makeISSARequest(worldPt, survey, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.ISSA, worldPt, survey, sizeInDeg);
        req.setTitle('ISSA '+survey);
        req.setDrawingSubGroupId('iras');
        return req;
    }

    //======================== IRIS =====================================


    /**
     * 
     * @param worldPt
     * @param {string} survey  must be one of '12','25','60','100'
     * @param sizeInDeg less then 12.5
     * @return {WebPlotRequest}
     */
    static makeIRISRequest(worldPt, survey, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.IRIS, worldPt, survey, sizeInDeg);
        req.setTitle('IRIS '+survey);
        req.setDrawingSubGroupId('iras');
        return req;
    }


    //======================== 2MASS =====================================

    /**
     *
     * @param wp
     * @param {string} survey  must be one of 'asky', 'askyw', 'sx', 'sxw', 'cal'
     * @param {string} band  must be one of 'j','h','k'
     * @param sizeInDeg less then .138 degrees (500 arcsec)
     * @return {WebPlotRequest}
     */
    static make2MASSRequest(wp, survey, band, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.TWOMASS, wp, survey, sizeInDeg);
        req.setTitle('2MASS '+band.toUpperCase());
        req.setParam(C.SURVEY_KEY_BAND, band + '');
        req.setDrawingSubGroupId('2mass');
        return req;
    }

    //======================== MSX =====================================


    /**
     *
     * @param wp
     * @param survey must be  '3','4','5','6'
     *          for 'A (8.28 microns)', 'C (12.13 microns)', 'D (14.65 microns)', 'E (21.3 microns)'
     *
     * @param sizeInDeg .1 to 1.5
     * @return {WebPlotRequest}
     */
    static makeMSXRequest(wp, survey, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.MSX, wp, survey, sizeInDeg);
        req.setTitle('MSX '+survey);
        req.setDrawingSubGroupId('msx');
        return req;
    }

    //======================== SDSS =====================================

    /**
     *
     * @param wp
     * @param band  one of: 'u' 'g' 'r' 'i' 'z'
     * @param sizeInDeg .016 to .25
     * @return {WebPlotRequest}
     */
    static makeSloanDSSRequest(wp, band, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.SDSS, wp, band, sizeInDeg);
        req.setTitle('SDSS '+band);
        req.setDrawingSubGroupId('sdss');
        return req;
    }
    //======================== DSS =====================================


    /**
     *
     * @param wp
     * @param {string} survey must be one of : poss2ukstu_red poss2ukstu_ir poss2ukstu_blue poss1_red poss1_blue quickv phase2_gsc2 phase2_gsc1
     * @param sizeInDeg .016 to .25
     * @return {WebPlotRequest}
     */
    static makeDSSRequest(wp, survey, sizeInDeg) {
        const req = this.makePlotServiceReq(ServiceType.DSS, wp, survey, sizeInDeg);
        req.setTitle(`DSS ${survey}`);
        req.setDrawingSubGroupId('dss');
        return req;

    }

    //======================== Wise =====================================


    /**
     *
     * @param wp
     * @param survey can be Atlas, 3a, 1b
     * @param band 1,2,3,4
     * @param sizeInDeg  .01 to 3
     * @return {WebPlotRequest}
     */
    static makeWiseRequest(wp, survey, band, sizeInDeg) {
        const req = this.makePlotServiceReq(ServiceType.WISE, wp, survey, sizeInDeg);
        req.setParam(C.SURVEY_KEY_BAND, band + '');
        const sDesc= survey.toLowerCase()==='3a' ? 'Atlas' : survey;
        req.setTitle('WISE: '+sDesc+ ', B'+ band);
        req.setDrawingSubGroupId('wise');
        return req;
    }

    //======================== ZTF =====================================

    /**
     *
     * @param wp
     * @param {string} survey  'ref'
     * @param {string} band  must be one of 'zg', 'zr'
     * @param sizeInDeg
     * @return {WebPlotRequest}
     */
    static makeZTFRequest(wp, survey, band, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.ZTF, wp, survey, sizeInDeg);
        req.setTitle('ZTF '+band.toUpperCase());
        req.setParam(C.SURVEY_KEY_BAND, band + '');
        req.setDrawingSubGroupId('ztf');
        return req;
    }
    //======================== PTF =====================================

    /**
     *
     * @param wp
     * @param {string} survey 'level2'
     * @param {string} band  must be one of '1' '2'
     * @param sizeInDeg
     * @return {WebPlotRequest}
     */
    static makePTFRequest(wp, survey, band, sizeInDeg) {
        const req= this.makePlotServiceReq(ServiceType.PTF, wp, survey, sizeInDeg);
        req.setTitle('PTF '+band.toUpperCase());
        req.setParam(C.SURVEY_KEY_BAND, band + '');
        req.setDrawingSubGroupId('ptf');
        return req;
    }


    //======================== Atlas =====================================
    /**
     *
     * @param wp
     * @param survey any atlas combination tables: schema.table, i.e. 'spitzer.seip_science'
     * @param band any atlas 'band_name' column, i.e 'IRAC2' (which is 2.4 microns channel from IRAC instrument)
     * @param filter extra filter for particular table, such as 'file_type = 'science' and fname like '%.mosaic.fits' or 'file_type = 'science' and principal=1'
     * @param sizeInDeg
     * @return {WebPlotRequest}
     */
    static makeAtlasRequest(wp, survey, band, filter, sizeInDeg) {
        const req = this.makePlotServiceReq(ServiceType.ATLAS, wp, survey, sizeInDeg);
        req.setParam(C.SURVEY_KEY, survey);
        req.setParam('filter', filter); //Needed for the query but not for fetching the data (see QueryIBE metadata)
        req.setParam(C.SURVEY_KEY_BAND, band + '');
        req.setTitle(survey + ',' + band);
        // TODO drawingSubGroupId TO BE SET OUTSIDE here! ATLAS has many dataset and it will depend on the app to group those images, example: See ImageSelectPanelResult.js, Finderrchart...
        //req.setDrawingSubGroupId(survey.split(".")[1]); // 'spitzer.seip_science'
        return req;
    }
    //======================== DSS or IRIS =====================================

    static makeDSSOrIRISRequest(wp, dssSurvey, IssaSurvey, sizeInDeg) {
        const r = this.makePlotServiceReq(ServiceType.DSS_OR_IRIS, wp, dssSurvey, sizeInDeg);
        r.setSurveyKeyAlt(IssaSurvey);
        return r;
    }

    //======================== All Sky =====================================


    static makeAllSkyPlotRequest() {
        return new WebPlotRequest(RequestType.ALL_SKY, 'All Sky Image');
    }



    //======================== Blank =====================================
    static makeBlankPlotRequest(wp, arcsecSize, plotWidth, plotHeight ) {
        const r= new WebPlotRequest(RequestType.BLANK, '');
        r.setWorldPt(wp);
        r.setBlankArcsecPerPix(arcsecSize);
        r.setBlankPlotWidth(plotWidth);
        r.setBlankPlotHeight(plotHeight);
        r.setGridOn(GridOnStatus.TRUE);
        return r;
    }

    //======================== HiPS =====================================
    static makeHiPSRequest(rootUrl, wp= undefined, sizeInDeg= 180) {
        const r= new WebPlotRequest(RequestType.HiPS, '');
        r.setHipsRootUrl(rootUrl);
        if (wp) r.setWorldPt(wp);
        r.setSizeInDeg(sizeInDeg);
        return r;
    }

//======================================================================
//----------------------- Title Settings -------------------------------
//======================================================================

    setTitle(title) { this.setParam(C.TITLE, title); }

    getTitle() { return this.getParam(C.TITLE); }


    setExpandedTitle(title) { this.setParam(C.EXPANDED_TITLE, title); }

    getExpandedTitle() { return this.getParam(C.EXPANDED_TITLE); }

    getUserDesc() { return this.getParam(C.USER_DESC); }

    /**
     *
     * @param option TitleOptions
     */
    setTitleOptions(option) { this.setParam(C.TITLE_OPTIONS,option.key); }

    /**
     *
     * @return {TitleOptions}
     */
    getTitleOptions() {
        return TitleOptions.get(this.getParam(C.TITLE_OPTIONS)) || TitleOptions.NONE;
    }



    /**
     *
     * @param option HeaderDecorationOps
     */
    setAnnotationOps(option) { this.setParam(C.ANNOTATION_OPS,option.key); }

    /**
     *
     * @return {AnnotationOps}
     */
    getAnnotationOps() {
        return AnnotationOps.get(this.getParam(C.ANNOTATION_OPS)) || AnnotationOps.INLINE;
    }




    /**
     *
     * @param {ExpandedTitleOptions} option
     */
    setExpandedTitleOptions(option) { this.setParam(C.EXPANDED_TITLE_OPTIONS,option.key); }

    /**
     *
     * @return {ExpandedTitleOptions}
     */
    getExpandedTitleOptions() {
        return ExpandedTitleOptions.get(this.getParam(C.EXPANDED_TITLE_OPTIONS)) || ExpandedTitleOptions.REPLACE;
    }




    setPreTitle(preTitle) { this.setParam(C.PRE_TITLE, preTitle); }
    getPreTitle() { return this.getParam(C.PRE_TITLE); }


    setPostTitle(postTitle) { this.setParam(C.POST_TITLE, postTitle); }
    getPostTitle() { return this.getParam(C.POST_TITLE); }

//======================================================================
//----------------------- Overlay Settings ------------------------------
//======================================================================

    /**
     *
     * @param {WorldPt|String} worldPt - the world point object or a serialized version
     */
    setOverlayPosition(worldPt) {
        this.setParam(C.OVERLAY_POSITION, worldPt ? worldPt.toString() : false);
    }

    /**
     *
     * @return {WorldPt}
     */
    getOverlayPosition() { return this.getWorldPtParam(C.OVERLAY_POSITION); }

    /**
     * @param {WorldPt|String} worldPt - the point object of the position to scroll image to when loaded
     */
    setInitialCenterPosition(worldPt) {
        this.setParam(C.INITIAL_CENTER_POSITION, worldPt ? worldPt.toString() : false);
    }

    /**
     *
     * @return {WorldPt}
     */
    getInitialCenterPosition() { return this.getWorldPtParam(C.INITIAL_CENTER_POSITION); }


//======================================================================
//----------------------- Color Settings ------------------------------
//======================================================================

    /**
     *
     * @param {int} id integer, color table id number
     */
    setInitialColorTable(id) { this.setParam(C.INIT_COLOR_TABLE, id + ''); }

    /**
     *
     * @return {int} color table id number
     */
    getInitialColorTable() {
        return this.getIntParam(C.INIT_COLOR_TABLE,0);
    }

    /**
     *
     * @param rangeValues RangeValues
     */
    setInitialRangeValues(rangeValues) {
        if (rangeValues) {
            this.setParam(C.INIT_RANGE_VALUES, rangeValues.toJSON());
        }
        else {
            this.removeParam(C.INIT_RANGE_VALUES);
        }
    }

    /**
     *
     * @return {RangeValues}
     */
    getInitialRangeValues() {
        return this.containsParam(C.INIT_RANGE_VALUES) ? RangeValues.parse(this.getParam(C.INIT_RANGE_VALUES)) : null;
    }

//======================================================================
//----------------------- Zoom Settings ------------------------------
//======================================================================

    /**
     * Certain zoom types require the width of the viewable area to determine the zoom level
     * used with ZoomType.FULL_SCREEN, ZoomType.TO_WIDTH
     *
     * @param {number} width the width in pixels
     * @see {ZoomType}
     */
    setZoomToWidth(width) {
        const w= Number(width);
        if (!isNaN(w)) this.setParam(C.ZOOM_TO_WIDTH, Math.round(w) + '');
    }

    getZoomToWidth() {
        return this.containsParam(C.ZOOM_TO_WIDTH) ? this.getIntParam(C.ZOOM_TO_WIDTH) : 0;
    }

    /**
     * Certain zoom types require the height of the viewable area to determine the zoom level
     * used with ZoomType.FULL_SCREEN, ZoomType.TO_HEIGHT (to height, no yet implemented)
     *
     * @param height the height in pixels
     * @see {ZoomType}
     */
    setZoomToHeight(height) {
        const h= Number(height);
        if (!isNaN(h)) this.setParam(C.ZOOM_TO_HEIGHT, Math.round(h) + '');
    }

    getZoomToHeight() {
        return this.containsParam(C.ZOOM_TO_HEIGHT) ? this.getIntParam(C.ZOOM_TO_HEIGHT) : 0;
    }


    /**
     * set the initialize zoom level, this is used with ZoomType.LEVEL
     *
     * @param {number} zl the zoom level, float
     * @see {ZoomType}
     */
    setInitialZoomLevel(zl) {
        this.setParam(C.INIT_ZOOM_LEVEL, zl + '');
    }

    /**
     *
     * @return {number}, the zoom level
     */
    getInitialZoomLevel() {
        return this.getFloatParam(C.INIT_ZOOM_LEVEL,1);
    }

    /**
     *
     * @param {boolean} hasMax
     */
    setHasMaxZoomLevel(hasMax) { this.setParam(C.HAS_MAX_ZOOM_LEVEL, hasMax +''); }

    /**
     *
     * @return {boolean}
     */
    hasMaxZoomLevel() { return this.getBooleanParam(C.HAS_MAX_ZOOM_LEVEL); }

    /**
     * sets the zoom type, based on the ZoomType other zoom set methods may be required
     * Notes for ZoomType:
     * <ul>
     * <li>ZoomType.LEVEL is the default when there is not width and height, when set you may optionally call
     * setInitialZoomLevel the zoom will default to be 1x</li>
     * <li>if ZoomType.TO_WIDTH then you must call setZoomToWidth and set a width </li>
     * <li>if ZoomType.FULL_SCREEN then you must call setZoomToWidth with a width and
     * setZoomToHeight with a height</li>
     * <li>if ZoomType.ARCSEC_PER_SCREEN_PIX then you must call setZoomArcsecPerScreenPix</li>
     * </ul>
     *
     * @param {ZoomType} zoomType affect how the zoom is computed
     * @see ZoomType
     */
    setZoomType(zoomType) {
        if (zoomType) this.setParam(C.ZOOM_TYPE, zoomType.key);
    }

    getZoomType() {
        const w= this.getZoomToWidth();
        const h= this.getZoomToHeight();
        const defaultType= (w && h) ?  ZoomType.TO_WIDTH_HEIGHT : ZoomType.LEVEL;
        return ZoomType.get(this.getParam(C.ZOOM_TYPE)) || defaultType;
    }

    /**
     * set the arcseconds per screen pixel that will be used to determine the zoom level.
     * Used with ZoomType.ARCSEC_PER_SCREEN_PIX
     *
     * @param {number} arcsecSize
     * @see ZoomType
     */
    setZoomArcsecPerScreenPix(arcsecSize) {
        this.setParam(C.ZOOM_ARCSEC_PER_SCREEN_PIX, arcsecSize + '');
    }

    /**
     *
     * @return {number}
     */
    getZoomArcsecPerScreenPix() {
        return this.getFloatParam(C.ZOOM_ARCSEC_PER_SCREEN_PIX,0);
    }

//======================================================================
//----------------------- Rotate  & Flip Settings ----------------------
//======================================================================

    /**
     * Plot should come up rotated north
     *
     * @param {boolean} rotateNorth true to rotate
     */
    setRotateNorth(rotateNorth) { this.setParam(C.ROTATE_NORTH, rotateNorth + ''); }

    /**
     *
     * @return {boolean}
     */
    getRotateNorth() { return this.getBooleanParam(C.ROTATE_NORTH); }

    /**
     * Plot should come up rotated north, unless the user has already set the rotation using the button
     *
     * @param {boolean} rotateNorth true to rotate
     */
    setRotateNorthSuggestion(rotateNorth) {
        this.setParam(C.ROTATE_NORTH_SUGGESTION, rotateNorth + '');
    }

    /**
     *
     * @return {boolean}
     */
    getRotateNorthSuggestion() { return this.getBooleanParam(C.ROTATE_NORTH_SUGGESTION); }

    /**
     * Set to coordinate system for rotate north, eq j2000 is the default
     *
     * @param {boolean} rotateNorthType CoordinateSys, default CoordinateSys.EQ_J2000
     */
    setRotateNorthType(rotateNorthType) {
        this.setParam(C.ROTATE_NORTH_TYPE, rotateNorthType.toString());
    }

    /**
     *
     * @return CoordinateSys
     */
    getRotateNorthType() {
        const cStr = this.getParam(C.ROTATE_NORTH_TYPE);
        let retval = null;
        if (cStr !== null) retval = CoordinateSys.parse(cStr);
        if (retval === null) retval = CoordinateSys.EQ_J2000;
        return retval;
    }

    /**
     * set to rotate, if true, the angle should also be set
     *
     * @param rotate boolean, true to rotate
     */
    setRotate(rotate) { this.setParam(C.ROTATE, rotate + ''); }

    /**
     * @return boolean,  true if rotate, false otherwise
     */
    getRotate() { return this.getBooleanParam(C.ROTATE); }


    /**
     * set the angle to rotate to
     *
     * @param rotationAngle  number, the angle in degrees to rotate to
     */
    setRotationAngle(rotationAngle) { this.setParam(C.ROTATION_ANGLE, rotationAngle + ''); }

    /**
     * @return number, the angle
     */
    getRotationAngle() { return this.getFloatParam(C.ROTATION_ANGLE); }

    setRotateFromNorth(fromNorth) { this.setParam(C.ROTATE_FROM_NORTH, fromNorth+ ''); }

    getRotateFromNorth() { this.getBooleanParam(C.ROTATE_FROM_NORTH,true); }

    /**
     * set if this image should be flipped on the Y axis
     * @param flipY boolean, true to flip, false not to flip
     */
    setFlipY(flipY) { this.setParam(C.FLIP_Y,flipY+''); }


    /**
     * @return boolean, flip true or false
     */
    isFlipY() { return this.getBooleanParam(C.FLIP_Y); }

    /**
     * set if this image should be flipped on the X axis
     * @param flipX boolean, true to flip, false not to flip
     */
    setFlipX(flipX) { this.setParam(C.FLIP_X,flipX+''); }


    /**
     * @return boolean, flip true or false
     */
    isFlipX() { return this.getBooleanParam(C.FLIP_X); }

//======================================================================
//----------------------- Crop Settings --------------------------------
//======================================================================

    /**
     * Crop the image before returning it.  If rotation is set then the crop will happen post rotation.
     * Note: setCropPt1 & setCropPt2 are required to crop
     *
     * @param postCrop boolean, do the post crop
     */
    setPostCrop(postCrop) { this.setParam(C.POST_CROP, postCrop + ''); }

    /**
     * @return boolean, do the post crop
     */
    getPostCrop() { return this.getBooleanParam(C.POST_CROP); }


    /**
     * set the post crop
     * @param postCrop boolean
     */
    setPostCropAndCenter(postCrop) { this.setParam(C.POST_CROP_AND_CENTER, postCrop + ''); }

    /**
     * @return boolean, do the post crop and center
     */
    getPostCropAndCenter() { return this.getBooleanParam(C.POST_CROP_AND_CENTER); }

    /**
     * Set to coordinate system for crop and center, eq j2000 is the default
     * @param csys CoordinateSys, the CoordinateSys, default CoordinateSys.EQ_J2000
     */
    setPostCropAndCenterType(csys) { this.setParam(C.POST_CROP_AND_CENTER_TYPE, csys.toString()); }

    /**
     * @return CoordinatesSys
     */
    getPostCropAndCenterType() {
        const cStr= this.getParam(C.POST_CROP_AND_CENTER_TYPE);
        let retval= null;
        if (cStr!==null) retval= CoordinateSys.parse(cStr);
        if (retval===null) retval= CoordinateSys.EQ_J2000;
        return retval;
    }

    setCropPt1(pt1) {
        if (pt1) this.setParam((pt1.type===Point.W_PT) ? C.CROP_WORLD_PT1 : C.CROP_PT1, pt1.toString());
    }

    /**
     *  @return imagePt
     */
    getCropImagePt1() { return parseImagePt(this.getParam(C.CROP_PT1)); }

    setCropPt2(pt2) {
        if (pt2) this.setParam((pt2.type===Point.W_PT) ? C.CROP_WORLD_PT2 : C.CROP_PT2, pt2.toString());
    }

    /**
     *  @return imagePt
     */
    getCropImagePt2() { return parseImagePt(this.getParam(C.CROP_PT2)); }


    /**
     *  @return WorldPt
     */
    getCropWorldPt1() { return this.getWorldPtParam(C.CROP_WORLD_PT1); }

    /**
     *  @return WorldPt
     */
    getCropWorldPt2() { return this.getWorldPtParam(C.CROP_WORLD_PT2); }



//======================================================================
//----------------------- Blank Image Settings -------------------------
//======================================================================

    /**
     * set the arc seconds per pixel that will be used for a blank image
     * Used with RequestType.BLANK
     *
     * @param {number} arcsecSize float,  the size of the pixels in arcsec
     * @see RequestType
     */
    setBlankArcsecPerPix(arcsecSize) { this.setParam(C.BLANK_ARCSEC_PER_PIX, arcsecSize + ''); }

    /**
     * @return {number} float
     */
    getBlankArcsecPerPix() { return this.getFloatParam(C.BLANK_ARCSEC_PER_PIX,0); }

    /**
     * @param {number} width int
     */
    setBlankPlotWidth(width) { this.setParam(C.BLANK_PLOT_WIDTH, width + ''); }

    /**
     * @return {number} width int
     */
    getBlankPlotWidth() {
        return this.getIntParam(C.BLANK_PLOT_WIDTH,0);
    }


    /**
     * @param height int
     */
    setBlankPlotHeight(height) { this.setParam(C.BLANK_PLOT_HEIGHT, height + ''); }

    getBlankPlotHeight() { return this.getIntParam(C.BLANK_PLOT_HEIGHT,0); }

//======================================================================
//----------------------- Retrieval Settings --------------------------------
//======================================================================

    /**
     * plot the file name that exist on the server
     *
     * @param fileName the file name on the server
     */
    setFileName(fileName) { this.setParam(C.FILE, fileName); }

    getFileName() { return this.getParam(C.FILE); }

    /**
     * retrieve and plot the file from the specified URL
     *
     * @param url the URL where the file resides
     */
    setURL(url) { this.setSafeParam(C.URLKEY, url); }

    getURL() { return this.getSafeParam(C.URLKEY); }

    setURLCheckForNewer(check) { this.setSafeParam(C.URL_CHECK_FOR_NEWER, check+''); }

    getURLCheckForNewer() { return this.getBooleanParam(C.URL_CHECK_FOR_NEWER); }

    /**
     *
     * @param service ServiceType
     */
    setServiceType(service) { this.setParam(C.SERVICE, service.key); }

    /**
     * @return RequestType
     */
    getRequestType() {
        return RequestType.get(this.getParam(C.TYPE)) ||RequestType.FILE;
    }

    /**
     * Set the type of request. This parameter is required for every call.  The factory methods will always
     * set the Request type.
     *
     *
     * @param type the RequestType
     * @see RequestType
     */
    setRequestType(type) { this.setParam(C.TYPE, type.key); }

    /**
     *
     * @return ServiceType
     */
    getServiceType() {
        return ServiceType.get(this.getParam(C.SERVICE)) ||ServiceType.NONE;
    }

    /**
     * @param {string} key string
     */
    setSurveyKey(key) { this.setParam(C.SURVEY_KEY, key); }

    /**
     * @return {string} key string
     */
    getSurveyKey() { return this.getParam(C.SURVEY_KEY); }

    /**
     * @param {string} key string
     */
    setSurveyKeyAlt(key) { this.setParam(C.SURVEY_KEY_ALT, key); }

    /**
     * @return {string} key string
     */
    getSurveyKeyAlt() { return this.getParam(C.SURVEY_KEY_ALT); }

    /**
     * @return {string} key string
     */
    getSurveyBand() { return this.getParam(C.SURVEY_KEY_BAND); }

//======================================================================
//----------------------- Object & Area Settings -----------------------
//======================================================================

    /**
     * @param {string} objectName string astronomical object to search
     */
    setObjectName(objectName) { this.setParam(C.OBJECT_NAME, objectName); }

    /**
     * @return {string} astronomical object, string
     */
    getObjectName() { return this.getParam(C.OBJECT_NAME); }


    /**
     *
     * @param resolver Resolver, name resolver type
     */
    setResolver(resolver) { this.setParam(C.RESOLVER, resolver.toString()); }

    /**
     * @return Resolver, name resolver type
     */
    getResolver() { return parseResolver(this.getParam(C.RESOLVER)); }

    /**
     *
     * @param {WorldPt} worldPt WorldPt
     */
    setWorldPt(worldPt) {
        if (worldPt) this.setParam(C.WORLD_PT, worldPt.toString());
    }

    /**
     * @return {WorldPt} WorldPt
     */
    getWorldPt() { return this.getWorldPtParam(C.WORLD_PT); }


    setSizeInDeg(sizeInDeg) { this.setParam(C.SIZE_IN_DEG, sizeInDeg + ''); }
    getSizeInDeg() { return this.getFloatParam(C.SIZE_IN_DEG, NaN); }

//======================================================================
//----------------------- Other Settings --------------------------------
//======================================================================


    setMultiImageIdx(idx) { this.setParam(C.MULTI_IMAGE_IDX, idx + ''); }

    /**
     * @return number index of image
     */
    getMultiImageIdx() { return this.getIntParam(C.MULTI_IMAGE_IDX,0); }


    /**
     * image extension list. ex: '3,4,5' for extension 3, 4, 5
     * @param idxS
     */
    setMultiImageExts(idxS) { this.setParam(C.MULTI_IMAGE_EXTS, idxS); }

    /**
     * return image extension list
     * @returns {*}
     */
    getMultiImageExts() { return this.getParam(C.MULTI_IMAGE_EXTS); }

    /**
     * key to store preferences in local cache
     * @param key String
     */
    setPreferenceColorKey(key) { this.setParam(C.PREFERENCE_COLOR_KEY, key); }

    getPreferenceColorKey() { return this.getParam(C.PREFERENCE_COLOR_KEY); }

    setPreferenceZoomKey(key) { this.setParam(C.PREFERENCE_ZOOM_KEY, key); }

    getPreferenceZoomKey() { return this.getParam(C.PREFERENCE_ZOOM_KEY); }


    /**
     * @param save boolean
     */
    setSaveCorners(save) { this.setParam(C.SAVE_CORNERS, save + ''); }

    /**
     * @return boolean
     */
    getSaveCorners() { return this.getBooleanParam(C.SAVE_CORNERS); }


    /**
     * @param allowImageSelection boolean
     */
    setAllowImageSelection(allowImageSelection) {
        this.setParam(C.ALLOW_IMAGE_SELECTION, allowImageSelection + '');
    }

    /**
     * @return boolean
     */
    isAllowImageSelection() { return this.getBooleanParam(C.ALLOW_IMAGE_SELECTION); }

    /**
     * @param allowImageSelectionCreateNew boolean
     */
    setHasNewPlotContainer(allowImageSelectionCreateNew) {
        this.setParam(C.HAS_NEW_PLOT_CONTAINER, allowImageSelectionCreateNew + '');
    }

    getHasNewPlotContainer() { this.getBooleanParam(C.HAS_NEW_PLOT_CONTAINER); }


    /**
     *
     * @param gridOnStatus GridOnStatus
     */

    setGridOn(gridOnStatus= GridOnStatus.FALSE) {
        const stat= GridOnStatus.get( String(gridOnStatus) );
        this.setParam(C.GRID_ON, stat.key);
    }

    /**
     *
     * @return GridOnStatus
     */
    getGridOn() {
        return GridOnStatus.get(this.getParam(C.GRID_ON)) ||GridOnStatus.FALSE;
    }

    ///**
    // * @param hideTitleZoomLevel boolean
    // */
    //setHideTitleDetail(hideTitleZoomLevel) { this.setParam(C.HIDE_TITLE_DETAIL, hideTitleZoomLevel + ''); }
    //
    ///**
    // * @return boolean
    // */
    //getHideTitleDetail() { return this.getBooleanParam(C.HIDE_TITLE_DETAIL); }

    /**
     * @param thumbnailSize int
     */
    setThumbnailSize(thumbnailSize) { this.setParam(C.THUMBNAIL_SIZE, thumbnailSize+''); }

    /**
     * @return int
     */
    getThumbnailSize() {
        return this.getIntParam(C.THUMBNAIL_SIZE, DEFAULT_THUMBNAIL_SIZE);
    }

    /**
     * For 3 color, if this request fails then keep trying to make a plot with the other request
     *
     * @param continueOnFail boolean
     */
    setContinueOnFail(continueOnFail) { this.setParam(C.CONTINUE_ON_FAIL, continueOnFail + ''); }

    isContinueOnFail() { return this.getBooleanParam(C.CONTINUE_ON_FAIL); }

    setUniqueKey(key) { this.setParam(C.UNIQUE_KEY, key); }

    getUniqueKey() { return this.getParam(C.UNIQUE_KEY); }

    setPlotToDiv(div) { this.setParam(C.PLOT_TO_DIV, div); }

    getPlotToDiv() { return this.getParam(C.PLOT_TO_DIV); }


    setHeaderKeyForTitle(headerKey) { this.setParam(C.HEADER_KEY_FOR_TITLE, headerKey); }

    getHeaderKeyForTitle() { return this.getParam(C.HEADER_KEY_FOR_TITLE); }


    /**
     * @param {boolean} showBars boolean
     */
    setShowScrollBars(showBars) { this.setParam(C.SHOW_SCROLL_BARS, showBars + ''); }

    /**
     * @return boolean
     */
    getShowScrollBars() { return this.getBooleanParam(C.SHOW_SCROLL_BARS); }

    setProgressKey(key) { this.setParam(C.PROGRESS_KEY,key); }

    getProgressKey() { return this.getParam(C.PROGRESS_KEY); }

    setRequestKey(key) { this.setParam(C.PROGRESS_KEY,key); }  // alias of setProgressKey

    getRequestKey() { return this.getParam(C.PROGRESS_KEY); } // alias of getProgressKey

    /**
     * @param minimalReadout boolean
     */
    setMinimalReadout(minimalReadout) { this.setParam(C.MINIMAL_READOUT,minimalReadout+''); }

    /**
     * @return boolean
     */
    isMinimalReadout() { return this.getBooleanParam(C.MINIMAL_READOUT); }

    setDrawingSubGroupId(id) { this.setParam(C.DRAWING_SUB_GROUP_ID,id); }

    getDrawingSubGroupId() { return this.getParam(C.DRAWING_SUB_GROUP_ID); }

    setRelatedTableRow(id) { this.setParam(C.RELATED_TABLE_ROW,id); }

    getRelatedTableRow() { return this.getIntParam(C.RELATED_TABLE_ROW,-1); }

    setRelatedTableId(id) { this.setParam(C.RELATED_TABLE_ID,id); }

    getRelatedTableId() { return this.getParam(C.RELATED_TABLE_ID); }


    setDownloadFileNameRoot(nameRoot) { this.setParam(C.DOWNLOAD_FILENAME_ROOT, nameRoot); }

    getDownloadFileNameRoot() { return this.getParam(C.DOWNLOAD_FILENAME_ROOT); }

    setPlotId(id) { this.setParam(C.PLOT_ID,id); }

    getPlotId() { return this.getParam(C.PLOT_ID); }

    setPlotGroupId(id) { this.setParam(C.PLOT_GROUP_ID,id); }

    getPlotGroupId() { return this.getParam(C.PLOT_GROUP_ID); }

    setGroupLocked(locked) { this.setParam(C.GROUP_LOCKED,locked); }

    isGroupLocked() { return this.getBooleanParam(C.GROUP_LOCKED,true); }

    /**
     * Set the order that the image processing pipeline runs when it reads a fits file.
     * This is experimental.  Use at your own risk.
     * Warning- if you exclude an Order element, the pipeline will not execute that process
     * even is you have it set in the options.
     * @param orderList array of Order enums, the order of the pipeline
     */
    setPipelineOrder(orderList) {
        this.setParam(C.PIPELINE_ORDER, join(orderList,';'));
    }

    /**
     * @return {Array} array of Order enums
     */
    getPipelineOrder() {
        let retList= DEF_ORDER;
        if (this.containsParam(C.PIPELINE_ORDER)) {
            retList= makeOrderList(this.getParam(C.PIPELINE_ORDER));
            if (retList.length<2) retList= DEF_ORDER;
        }
        return retList;
    }


    setMaskBits(idx) { this.setParam(C.MASK_BITS,idx+''); }
    getMaskBits() { return this.containsParam(C.MASK_BITS) ? this.getIntParam(C.MASK_BITS) : 0;}

    setPlotAsMask(plotAsMask) { this.setParam(C.PLOT_AS_MASK, plotAsMask+''); }
    isPlotAsMask() { return this.getBooleanParam(C.PLOT_AS_MASK); }


    setMaskColors(colors) {
        if (isArray(colors)) {
            this.setParam(C.MASK_COLORS, join(colors, ';'));
        }
        else {
            this.setParam(C.MASK_COLORS, colors);
        }
    }

    getMaskColors() {
        const retList= [];
        if (this.containsParam(C.MASK_COLORS)) {
            return this.getParam(C.MASK_COLORS).split(';');
        }
        return retList;
    }

    setMaskRequiredWidth(width) { this.setParam(C.MASK_REQUIRED_WIDTH, width+''); }

    getMaskRequiredWidth() { return this.getIntParam(C.MASK_REQUIRED_WIDTH,0); }

    setMaskRequiredHeight(height) { this.setParam(C.MASK_REQUIRED_HEIGHT, height+''); }

    getMaskRequiredHeight() { return this.getIntParam(C.ASK_REQUIRED_HEIGHT,0); }


    setHipsRootUrl(url) { this.setSafeParam(C.HIPS_ROOT_URL, url);}
    getHipsRootUrl() { return this.getSafeParam(C.HIPS_ROOT_URL);}

    /**
     *
     * @param overlayIdList
     */
    setOverlayIds(overlayIdList) {
        this.setParam(C.OVERLAY_IDS, join(overlayIdList, ';'));
    }

    /**
     * @return {Array.<String>} array of string DrawLayerType IDs
     */
    getOverlayIds() {
        if (this.containsParam(C.OVERLAY_IDS)) {
            return this.getParam(C.OVERLAY_IDS).split(';');
        }
        else {
            return this.getRequestType()!==RequestType.HiPS ? DEFAULT_IMAGE_OVERLAYS : DEFAULT_HIPS_OVERLAYS;
        }
    }



    /**
     * Return the request area
     * i am using circle but it is really size not radius - todo: fix this
     *
     * @return an area to select
     */
    getRequestArea() {
        let retval = null;
        const wp= this.getWorldPt();
        const side = this.getSizeInDeg();
        if (wp) retval = {center:wp, radius:side};
        return retval;
    }


    prettyString() {
        let s = 'WebPlotRequest: ';
        switch (this.getRequestType()) {
            case RequestType.SERVICE:
                switch (this.getServiceType()) {
                    case ServiceType.IRIS:
                    case ServiceType.DSS:
                    case ServiceType.TWOMASS:
                        if (this.containsParam(C.WORLD_PT)) {
                            s += this.getServiceType().key + '- ' + this.getRequestArea();
                        }
                        else {
                            s += this.getServiceType().key + '- Obj name: ' + this.getObjectName() +
                            ', radius: ' +this.getParam(C.SIZE_IN_DEG);
                        }
                        break;
                }
                break;
            case RequestType.FILE:
                s += ' File: ' + this.getFileName();
                break;
            case RequestType.URL:
                s += ' URL: ' + this.getURL();
                break;
            case RequestType.ALL_SKY:
                s += ' AllSky';
                break;
            case RequestType.PROCESSOR:
                s += 'File Search Processor: '+ this.getRequestId();
                break;
        }
        return s;
    }

    setPlotDescAppend(s) { this.setParam(C.PLOT_DESC_APPEND, s); }

    getPlotDescAppend() { return this.getParam(C.PLOT_DESC_APPEND); }

    toStringServerSideOnly() {
        const retReq= this.makeCopy();
        clientSideKeys.forEach( (k) => retReq.params[k]= undefined);
        return retReq.toString();
    }

    /**
     * @return boolean
     */
    hasID() {
        return this.containsParam(this.ID_KEY) && this.getRequestId()!==ID_NOT_DEFINED;
    }
//======================================================================
//------------------ Private / Protected Methods -----------------------
//======================================================================

    /**
    * This method can only be used for those services that take the standard
    * ra, dec, radius approach (so far, iras, issa, 2mass, dss)
    *
    * @param {ServiceType} serviceType the network service type
    * @param {WorldPt} wp the center point WorldPt
     * @param {string} survey
    * @param {number} sizeInDeg size in degrees
    *
    * @return {WebPlotRequest} the PlotRequest object that was constructed
    */
    static makePlotServiceReq(serviceType, wp, survey, sizeInDeg) {
        const desc = this.makeServiceReqDesc(serviceType, survey, sizeInDeg);
        const req = new WebPlotRequest(RequestType.SERVICE, desc, serviceType);
        req.setSurveyKey(survey);
        req.setWorldPt(wp);
        req.setSizeInDeg(sizeInDeg);
        return req;
    }

    static makeServiceReqDesc(serviceType, survey, sizeInDeg) {
        return serviceType.key + ': ' + survey + ', ' + sizeInDeg + ' Deg';
    }


    /**
     * Parses the string argument into a ServerRequest object.
     * This method is reciprocal to toString().
     *
     * @param {string} str the serialized WebPlotRequest
     * @return (WebPlotRequest) the deserialized WebPlotRequest
     */
    static parse(str) {
        return ServerRequest.parse(str, new WebPlotRequest());
    }

    makeCopy() {
        const retval = new WebPlotRequest();
        retval.copyFrom(this);
        return retval;
    }

    static makeCopyOfRequest(r) {
        return r ? null : r.makeCopy();
    }

    static getAllKeys() { return allKeys; }

    static getClientKeys() { return clientSideKeys; }

    /**
     * Perform equals but ignore layout params, such as zoom type, width and height
     * @param obj
     * @return {boolean}
     */
    equalsPlottingParams(obj) {
        let retval= false;
        if (obj instanceof WebPlotRequest) {
            const wpr1= this.makeCopy();
            const wpr2= obj.makeCopy();
            ignoreForEquals.forEach((key)=> {
                wpr1.removeParam(key);
                wpr2.removeParam(key);
            });
            retval= wpr1.toString()===wpr2.toString();
        }
        return retval;
    }

// =====================================================================
// -------------------- Factory Methods --------------------------------
// =====================================================================

}


export const WPConst= C;
export default WebPlotRequest;

/**
 * Create a new object with the keys more consistent with the keys defined in WebPlotRequest.
 * if a case insensitive version of the key exist then replace it with the proper one, otherwise
 * use the key.
 * The loop looks up the key with case insensitive matching, if it does not exist it uses the original key
 * @param {Object} r - plain object
 * @return {Object}
 */
function cleanupObj(r) {
    return Object.keys(r).reduce( (obj,k) => {
        obj[get(allKeys.get(k), 'key',k)]= r[k]; // note - uses both lodash.get and enum.get
        return obj;
    },{});
}

/**
 * find all the invalid WebPlotRequest keys.  
 * @param {Object} r an object literal to evaluate
 * @return {String[]} an array of invalid keys
 */
export function findInvalidWPRKeys(r) {
    return Object.keys(r).filter( (k) => !Boolean(allKeys.get(k)));
}


/**
 * take a plain object plot request and clean it up has a minimum required 
 * to be valid
 * @param {Object|Array} request
 * @param global
 * @param fallbackGroupId
 * @param {Function} makePlotId make a plot id
 * @return {*}
 */
export function confirmPlotRequest(request,global,fallbackGroupId,makePlotId) {
    if (isArray(request)) {
        let locked= true;
        const idx= request.findIndex( (r) => r.plotId);

        const plotId= (idx>=0) ? request[idx].plotId : makePlotId();
        let plotGroupId;

        if (idx>=0  && request[idx].plotGroupId) {
            plotGroupId= request[idx].plotGroupId;
            locked= true;
        }
        else {
            plotGroupId= fallbackGroupId;
            locked= false;
        }

        return request.map( (r) => Object.assign({},global,r,{plotId,plotGroupId,GroupLocked:locked}));
    }
    else {
        const r= Object.assign({}, global, request);
        if (!r.plotId) r.plotId= makePlotId();
        if (!r.plotGroupId) r.plotGroupId= fallbackGroupId;
        if (r.plotGroupId===fallbackGroupId) r.GroupLocked= false;
        return r;
    }
}


function makeDataOnlyRequestString(r) {
    if (!r) return '';
    r= r.makeCopy();
    r.setZoomToWidth(1);
    r.setZoomToHeight(1);
    r.setRequestKey('');
    r.setInitialRangeValues();
    r.setInitialColorTable(0);
    return r.toString();
}

/**
 * Make simplified versions of the WebPlotRequest and compare them.
 * This function takes out a lot of parameters that are not related to resolving the fits file.  It attempts see
 * if we are requesting the same fits file or fits file services.
 * @param {WebPlotRequest} r1
 * @param {WebPlotRequest} r2
 */
export const isImageDataRequeestedEqual= (r1,r2) => makeDataOnlyRequestString(r1)===makeDataOnlyRequestString(r2);