	PRO UPD_CDS_POINT, QLDS, NOROLL=K_NOROLL, CENTER=CENTER,	$
		ERRMSG=ERRMSG, ADJUST=ADJUST
;+
; Project     :	SOHO - CDS
;
; Name        :	UPD_CDS_POINT
;
; Purpose     :	Update pointing in CDS data structure
;
; Category    :	CDS, Calibration, Class3
;
; Explanation :	Updates the pointing information in a CDS quicklook data
;		structure, based on information in a calibration database which
;		relates the OPS positions to pointing.
;
; Syntax      :	UPD_CDS_POINT, QLDS
;
; Examples    :
;
; Inputs      :	QLDS = A CDS quicklook data structure.
;
; Opt. Inputs :	None.
;
; Outputs     :	The modified structure is returned in place of the original
;		structure.
;
; Opt. Outputs:	None.
;
; Keywords    :	NOROLL = If set, then the spacecraft roll angle is ignored in
;			 determining the pointing.  This is the default for all
;			 roll angle smaller than 3 degrees prior to 2010-10-29.
;			 To enable the use of the spacecraft roll angle, use
;			 NOROLL=0
;
;		CENTER = The pointing to use for the center of the image as
;			 [XCEN, YCEN].  Overrides the values derived from the
;			 mechanism positions.
;
;		ADJUST = Adjust the position to account for the difference
;			 between the solar photospheric and helium radii.  The
;			 calibration of the CDS pointing places the apparent
;			 solar limb in helium 304 and 584 at the standard solar
;			 radius as given by PB0R().  However, the true solar
;			 photospheric limb should be somewhere interior to the
;			 helium limb.  The ADJUST keyword takes care of this
;			 discrepency.  There are three possible ways that this
;			 keyword can be used:
;
;			 ADJUST=value		Where the value is the amount
;						to adjust the pointing by.
;						Only values >1 will be used.
;
;			 ADJUST=1 or /ADJUST	The default adjustment value of
;						1.01283 will be used.
;
;			 ADJUST=0		Don't make the adjustment.
;
;		ERRMSG = If defined and passed, then any error messages will be
;			 returned to the user in this parameter rather than
;			 depending on the MESSAGE routine in IDL.  If no errors
;			 are encountered, then a null string is returned.  In
;			 order to use this feature, ERRMSG must be defined
;			 first, e.g.
;
;				ERRMSG = ''
;				UPD_CDS_POINT, QLDS, ERRMSG=ERRMSG
;				IF ERRMSG NE '' THEN ...
;
; Calls       :	POLY_FIT, GET_CDS_PNTCAL, FXPAR, DATATYPE
;
; Common      :	None.
;
; Restrictions:	Assumes that the CDHS is in OPS or sunsensor pointing mode,
;		rather than internal mode.  Otherwise, the OPS values are
;		simply commanded values, rather than actual values.  Data taken
;		prior to 23 May 1996 were with the CDHS in internal mode.
;
;		Also assumes that the spacecraft pointing information in the
;		header is correct.
;
;		If the data was taken with feature tracking on, then the OPS
;		positions are extrapolated to the start of the observation.
;		Otherwise, the average OPS position is used.
;
; Side effects:	The parameters in the HDRTEXT part of the quicklook structure
;		are *not* updated.
;
; Prev. Hist. :	None.
;
; History     :	Version 1, 25-Sep-1996, William Thompson, GSFC
;		Version 2, 11-Oct-1996, William Thompson, GSFC
;			Correct so that missing INS_X and INS_Y values are not
;			modified.
;		Version 3, 20-May-1997, William Thompson, GSFC
;			Added keyword NOROLL
;		Version 4, 29-Jul-1997, William Thompson, GSFC
;			Make even more sophisticated when OPS moves during
;			observation.
;		Version 5, 08-Aug-1997, William Thompson, GSFC
;			Added keyword CENTER
;		Version 6, 23-Aug-1997, William Thompson, GSFC
;			Fixed bug where one OPS position showed considerable
;			variation, while the other was rock steady.
;		Version 7, 30-Oct-1997, William Thompson, GSFC
;			Made NOROLL=1 the default.
;			Fixed bug involving applying UPD_CDS_POINT after
;			NOROLL=1 has already been applied.
;			Don't modify HDRTEXT--allow NOROLL=0 to be applied
;			to undo NOROLL=1.
;		Version 8, 25-Nov-1997, William Thompson, GSFC
;			Also made NOROLL=1 default for ROTATION tag within
;			window structures.
;		Version 9, 17-Dec-1997, William Thompson, GSFC
;			Added keyword ADJUST
;		Version 10, 12-Feb-1999, William Thompson, GSFC
;			Changed default to NOROLL=0 for all data after
;			19-Oct-1996.  This change is necessary for data taken
;			immediately after the SOHO recovery.
;		Version 11, 21-Apr-1999, William Thompson, GSFC
;			Determine default behavior of roll based on value
;			rather than date.  Assume all roll values below 3
;			degrees are really zero.
;		Version 12, 14-Jun-1999, William Thompson, GSFC
;			Make default adjustment 1.01283.
;       Version 13, 12-Jun-2003, William Thompson, GSFC
;           Fix bug with using /ADJUST multiple times.
;           Make /ADJUST the default.
;       Version 14, 01-Jul-2003, William Thompson, GSFC
;           Rotate coordinate system by 180 degrees when
;           upside-down.
;       Version 15, 14-Jul-2003, William Thompson, GSFC
;           Fixed bug in version 14 when called twice.
;       Version 16, 12-Mar-2008, WTT, Don't apply to Level-1 files
;       Version 17, 01-Nov-2010, WTT, use roll angle after 2010-10-29.
;       Version 18, 29-Apr-2021, WTT
;           Check header for keyword DATASRC. If 'Level 1' bail out.
;           Don't do extra flip if (Bogart AND flipped).
;
; Contact     :	WTHOMPSON
;-
;
	IF N_PARAMS() NE 1 THEN BEGIN
	    MESSAGE = 'Syntax:  UPD_CDS_POINT, QLDS'
	    GOTO, HANDLE_ERROR
	ENDIF
;
;  Don't apply to Level-1 files.
;
        IF STRMID(FXPAR(QLDS.HDRTEXT,'DATASRC'),0,7) EQ 'Level 1' THEN BEGIN
            MESSAGE, /INFORMATIONAL, 'Not applied to Level-1 files'
            RETURN
        ENDIF
;
;  Extract the OPS_L and OPS_R data from the quicklook data structure.  Sort by
;  start time, and remove any missing values.  If they're all missing, then use
;  the values from the header (commanded).
;
	OPS_L = QLDS.OPS_LDATA
	OPS_R = QLDS.OPS_RDATA
	DEL_TIME = QLDS.DEL_TIMEDATA
	W = WHERE((OPS_L    NE QLDS.OPS_LDESC.MISSING) AND	$
		  (OPS_R    NE QLDS.OPS_RDESC.MISSING) AND	$
		  (DEL_TIME NE QLDS.DEL_TIMEDESC.MISSING), COUNT)
	IF COUNT EQ 0 THEN BEGIN
		OPS_L = QLDS.HEADER.OPS_L
		OPS_R = QLDS.HEADER.OPS_R
		DEL_TIME = 0
	END ELSE BEGIN
		OPS_L    = OPS_L(W)
		OPS_R    = OPS_R(W)
		DEL_TIME = DEL_TIME(W)
	ENDELSE
;
	IF N_ELEMENTS(DEL_TIME) GT 1 THEN BEGIN
		S = SORT(DEL_TIME)
		OPS_L    = OPS_L(S)
		OPS_R    = OPS_R(S)
		DEL_TIME = DEL_TIME(S)
	ENDIF
;
;  If the data don't change by more than two, then simply average them.
;  Otherwise, extrapolate to DEL_TIME=0.
;
	IF ((MAX(OPS_L)-MIN(OPS_L)) LE 2) AND ((MAX(OPS_R)-MIN(OPS_R)) LE 2) $
		THEN BEGIN
	    OPS_L = AVERAGE(OPS_L)
	    OPS_R = AVERAGE(OPS_R)
	END ELSE BEGIN
	    WW = INDGEN(N_ELEMENTS(OPS_L))
	    REPEAT BEGIN
		N = N_ELEMENTS(WW)
		DEL_TIME = DEL_TIME(WW)
		OPS_L = OPS_L(WW)
		OPS_R = OPS_R(WW)
		PL = POLY_FIT(DEL_TIME, OPS_L, 1)
		PR = POLY_FIT(DEL_TIME, OPS_R, 1)
		DL = OPS_L - PL(0) - PL(1)*DEL_TIME  &  SL = STDEV(DL) > 0.01
		DR = OPS_R - PR(0) - PR(1)*DEL_TIME  &  SR = STDEV(DR) > 0.01
		WW = WHERE((ABS(DL) LT 3*SL) AND (ABS(DR) LT 3*SR))
	    ENDREP UNTIL N EQ N_ELEMENTS(WW)
	    OPS_L = PL(0)
	    OPS_R = PR(0)
	ENDELSE
;
;  Get the pointing based on this OPS position.  Compare it against INS_X0,
;  INS_Y0 in the header.  Replace INS_X0, INS_Y0 in the header.
;
	MESSAGE = ''
	GET_CDS_PNTCAL, QLDS.HEADER.DATE_OBS, OPS_L, OPS_R, X, Y,	$
		ERRMSG=MESSAGE
	IF MESSAGE NE '' THEN GOTO, HANDLE_ERROR
	XCOR = X - QLDS.HEADER.INS_X0  &  QLDS.HEADER.INS_X0 = X
	YCOR = Y - QLDS.HEADER.INS_Y0  &  QLDS.HEADER.INS_Y0 = Y
;
;  Get the spacecraft pointing.
;
	SC_X0   = QLDS.HEADER.SC_X0
	SC_Y0   = QLDS.HEADER.SC_Y0
	SC_ROLL = QLDS.HEADER.SC_ROLL
;
;  From the original parameters in the header text, determine the offset
;  between INS_X0,INS_Y0, and XCEN,YCEN, without the spacecraft roll.  Add this
;  offset to the INS_X0,INS_Y0 values from the unmodified header structure.
;
	XCEN = FXPAR(QLDS.HDRTEXT,'XCEN')
	YCEN = FXPAR(QLDS.HDRTEXT,'YCEN')
	ANGLE = FXPAR(QLDS.HDRTEXT,'ANGLE')
	XX = XCEN - SC_X0
	YY = YCEN - SC_Y0
	COS_A = COS(ANGLE / !RADEG)
	SIN_A = SIN(ANGLE / !RADEG)
	XCEN =  XX*COS_A + YY*SIN_A + X - XCOR - FXPAR(QLDS.HDRTEXT,'INS_X0')
	YCEN = -XX*SIN_A + YY*COS_A + Y - YCOR - FXPAR(QLDS.HDRTEXT,'INS_Y0')
;
;  If the spacecraft is upside-down, then rotate the coordinate system by 180
;  degrees.
;
        IF COS(ANGLE*!DTOR) LT 0 THEN BEGIN
            SC_ROLL = (SC_ROLL - 180) MOD 360
            ANGLE = (ANGLE - 180) MOD 360
            QLDS.HEADER.SC_ROLL = SC_ROLL
            QLDS.HEADER.ANGLE   = ANGLE
            QLDS.DETDESC.SPACING[1:2] = -ABS(QLDS.DETDESC.SPACING[1:2])
            XCEN = -XCEN  &  YCEN = -YCEN
            XCOR = -XCOR  &  YCOR = -YCOR
            INVERTED = 1
        END ELSE INVERTED = 0
;
;  If the NOROLL keyword was set, then set the spacecraft roll to be 0.
;
	IF N_ELEMENTS(K_NOROLL) EQ 1 THEN NOROLL=K_NOROLL ELSE BEGIN
	    IF (ABS(QLDS.HEADER.SC_ROLL) GE 3) OR $
              (QLDS.HEADER.DATE_OBS GE '2010-10-29') THEN $
              NOROLL=0 ELSE NOROLL=1
        ENDELSE
	IF KEYWORD_SET(NOROLL) THEN BEGIN
	    QLDS.HEADER.SC_ROLL = 0.
	    QLDS.HEADER.ANGLE   = 0.
	    ANGLE = 0.
	    COS_A = 1.
	    SIN_A = 0.
;
;  Otherwise, retrieve the original roll from the FITS header.
;
	END ELSE BEGIN
	    SC_ROLL = FXPAR(QLDS.HDRTEXT, 'SC_ROLL')
            ; jar: Bogart AND flipped!! Commented out the following line
            ; IF KEYWORD_SET(INVERTED) THEN SC_ROLL = (SC_ROLL - 180) MOD 360
	    ANGLE = SC_ROLL
	    QLDS.HEADER.SC_ROLL = SC_ROLL
	    QLDS.HEADER.ANGLE	= ANGLE
	    COS_A = COS(ANGLE / !RADEG)
	    SIN_A = SIN(ANGLE / !RADEG)
	ENDELSE
;
;  Apply the correction factor, and reapply the spacecraft pointing.
;
	XX = XCEN + XCOR
	YY = YCEN + YCOR
	XCEN = XX * COS_A  -  YY * SIN_A  +  SC_X0
	YCEN = XX * SIN_A  +  YY * COS_A  +  SC_Y0
;
;  If the ADJUST keyword was passed, then use the adjustment value to modify
;  the XCEN and YCEN values.  The default is to apply the standard adjustment.
;
        ADJ = 1.01283
	IF N_ELEMENTS(ADJUST) EQ 1 THEN BEGIN
	    IF ADJUST NE 0 THEN BEGIN
		IF ADJUST GT 1 THEN ADJ = ADJUST ELSE ADJ = 1.01283
            END ELSE ADJ = 1
	ENDIF
	XCEN = ADJ * XCEN
	YCEN = ADJ * YCEN
;
;  If the CENTER keyword was passed, then use those values instead.
;
	IF N_ELEMENTS(CENTER) EQ 2 THEN BEGIN
	    XCEN = CENTER(0)
	    YCEN = CENTER(1)
	ENDIF
;
;  The difference between the old and new values of XCEN, YCEN are the complete
;  correction factors.  Replace XCEN and YCEN in the header.
;
	XCOR = XCEN - QLDS.HEADER.XCEN  &  QLDS.HEADER.XCEN = XCEN
	YCOR = YCEN - QLDS.HEADER.YCEN  &  QLDS.HEADER.YCEN = YCEN
;
;  Correct the INS_X and INS_Y data arrays.
;
	W = WHERE(QLDS.INS_XDATA NE QLDS.INS_XDESC.MISSING, COUNT)
	IF COUNT GT 0 THEN QLDS.INS_XDATA(W) = QLDS.INS_XDATA(W) + XCOR
	W = WHERE(QLDS.INS_YDATA NE QLDS.INS_YDESC.MISSING, COUNT)
	IF COUNT GT 0 THEN QLDS.INS_YDATA(W) = QLDS.INS_YDATA(W) + YCOR
;
;  Now do the same for the pointings in the array descriptions.  First, select
;  out all the structure tags that end in the characters "DESC".
;
	NAMES = TAG_NAMES(QLDS)
	FOR I_TAG = 0,N_ELEMENTS(NAMES)-1 DO BEGIN
	    LEN = STRLEN(NAMES(I_TAG))
	    IF STRMID(NAMES(I_TAG), (LEN-4)>0, 4) EQ 'DESC' THEN BEGIN
;
;  Extract the axis labels and the origins.
;
		DESC = QLDS.(I_TAG)
		IF DATATYPE(DESC,1) EQ 'Structure' THEN BEGIN
		    AXES = DESC(0).AXES
		    ORIGIN = DESC.ORIGIN
		    ROTATION = DESC.ROTATION
;
;  Find where the SOLAR_X axis is, and modify its origin.  Do the same for
;  SOLAR_Y.  If the NOROLL keyword is set, or the coordinate system has been
;  inverted, then modify the rotation field.
;
		    W = WHERE(AXES EQ 'SOLAR_X', COUNT)
		    IF COUNT GT 0 THEN BEGIN
			ORIGIN(W,*) = ORIGIN(W,*) + XCOR
			ROTATION(W,*) = ANGLE
		    ENDIF
		    W = WHERE(AXES EQ 'SOLAR_Y', COUNT)
		    IF COUNT GT 0 THEN BEGIN
			ORIGIN(W,*) = ORIGIN(W,*) + YCOR
			ROTATION(W,*) = ANGLE
		    ENDIF
		    QLDS.(I_TAG).ORIGIN = ORIGIN
		    IF KEYWORD_SET(NOROLL) OR KEYWORD_SET(INVERTED) THEN $
			    QLDS.(I_TAG).ROTATION = ROTATION
		ENDIF
	    ENDIF
	ENDFOR
	RETURN
;
;  Error handling point.
;
HANDLE_ERROR:
	IF N_ELEMENTS(ERRMSG) NE 0 THEN ERRMSG = 'UPD_CDS_POINT: ' + MESSAGE $
		ELSE MESSAGE, MESSAGE
;
	END
