This file is indexed.

/usr/share/gnudatalanguage/astrolib/extast.pro is in gdl-astrolib 2018.02.16+dfsg-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
pro extast,hdr,astr,noparams, alt=alt, Has_CPDIS = has_cpdis, HAS_D2IMDIS= has_d2imdis
;+
; NAME:
;     EXTAST
; PURPOSE:
;     Extract ASTrometry parameters from a FITS image header.
; EXPLANATION:
;     Extract World Coordinate System information 
;     ( http://fits.gsfc.nasa.gov/fits_wcs.html ) from a FITS header and 
;     place it into an IDL structure.
;
; CALLING SEQUENCE:
;     EXTAST, hdr,  astr, [ noparams, ALT= ]   
;
; INPUT:
;     HDR - variable containing the FITS header (string array)
;
; OUTPUTS:
;     In the following, index 1 & 2 refer to the first and second astrometry
;     axes respectively. The actual axis numbers are stored in .AXIS
;
;     ASTR - Anonymous structure containing astrometry info from the FITS 
;             header ASTR always contains the following tags (even though 
;             some projections do not require all the parameters)
;      .NAXIS - 2 element array giving image size
;      .CD   -  2 x 2 array containing the astrometry parameters CD1_1 CD1_2
;               in DEGREES/PIXEL                                 CD2_1 CD2_2
;      .CDELT - 2 element double vector giving physical increment at the 
;                 reference pixel
;      .CRPIX - 2 element double vector giving X and Y coordinates of reference 
;               pixel (def = NAXIS/2) in FITS convention (first pixel is 1,1)
;      .CRVAL - 2 element double precision vector giving R.A. and DEC of 
;               reference pixel in DEGREES
;      .CTYPE - 2 element string vector giving projection types, default
;               ['RA---TAN','DEC--TAN']
;      .LONGPOLE - scalar giving native longitude of the celestial pole 
;             (default = 180 for zenithal projections) 
;      .LATPOLE - scalar giving native latitude of the celestial pole default=0)
;      .PV2 - Vector of projection parameters associated with latitude axis
;             PV2 will have up to 21 elements for the ZPN projection, up to 3 
;             for the SIN projection and no more than 2 for any other 
;             projection  
;
;     Fields added for version 2:
;      .PV1 - Vector of projection parameters associated with longitude axis
;      .AXES  - 2 element integer vector giving the FITS-convention axis 
;               numbers associated with astrometry, in ascending order. 
;               Default [1,2].
;      .REVERSE - byte, true if first astrometry axis is Dec/latitude
;      .COORD_SYS - 1 or 2 character code giving coordinate system, including
;                 'C' = RA/Dec, 'G' = Galactic, 'E' = Ecliptic, 'X' = unknown.
;      .PROJECTION  - 3-letter WCS projection code
;      .KNOWN    - true if IDL WCS routines recognise this projection
;      .RADECSYS - String giving RA/Dec system e.g. 'FK4', 'ICRS' etc.
;      .EQUINOX  - Double giving the epoch of the mean equator and equinox
;      .DATEOBS  - Text string giving (start) date/time of observations
;      .MJDOBS   - Modified julian date of start of observations.
;      .X0Y0     - Implied offset in intermediate world coordinates (x,y)
;                  if a non-standard fiducial point is set via PV1 and also
;                  PV1_0a =/ 0, indicating that an offset should be
;                  applied to place CRVAL at the (x,y) origin.
;                  Should be *added* to the IWC derived from application of
;                  CRPIX, CDELT, CD to the pixel coordinates.
;
;      .DISTORT - optional substructure specifying any distortion parameters
;                 currently implement only for "SIP" (Spitzer Imaging 
;                 Polynomial), "TPV" (tangent PV* polynomial) distortion 
;                 parameters, and "TNX" (tangent plus iraf distortion)
;
;       NOPARAMS -  Scalar indicating the results of EXTAST
;            -1 = Failure - Header missing astrometry parameters
;             1 = Success - Header contains CROTA + CDELT (AIPS-type) astrometry
;             2 = Success - Header contains CDn_m astrometry, rec.    
;             3 = Success - Header contains PCn_m + CDELT astrometry. 
;             4 = Success - Header contains ST  Guide Star Survey astrometry
;                           (see gsssextast.pro )
; OPTIONAL INPUT/OUTPUT KEYWORDS:
;       ALT -  single character 'A' through 'Z' or ' ' specifying an alternate 
;              astrometry system present in the FITS header.    The default is
;              to use the primary astrometry or ALT = ' '.   If /ALT is set, 
;              then this is equivalent to ALT = 'A'.   See Section 3.3 of 
;              Greisen & Calabretta (2002, A&A, 395, 1061) for information about
;              alternate astrometry keywords.    If not set on input, then
;              ALT is set to ' ' on output.
; OPTIONAL OUTPUT KEYWORD:
;		HAS_D2IMDIS = set to 1 if the FITS header includes a D2IMDISi keyword indicating that 
;			there is an array of (detector to image) pixel distortion corrections located in
;           another extension.  This use of a distortion lookup table is used by a couple of 
;           Hubble instruments.     If this keyword is not supplied, but the header 
;           contains D2IMDISi keywords then a warning message will be supplied, since
;           this distortion information will be missing from the astrometry structure.
;		HAS_CPDIS = set to 1 if the FITS header includes a CPDISi keyword indicating that 
;			there is an array of pixel distortion corrections located in another extension.
;			This use of a distortion lookup table is based on the draft proposal 
;           https://fits.gsfc.nasa.gov/wcs/dcs_20040422.pdf and is used by a couple of 
;           Hubble instruments.     If this keyword is not supplied, but the header 
;           contains CPDISi keywords then a warning message will be supplied, since
;           this distortion information will be missing from the astrometry structure.
; PROCEDURE:
;       EXTAST checks for astrometry parameters in the following order:
;
;       (1) the CD matrix PC1_1,PC1_2...plus CDELT*, CRPIX and CRVAL
;       (2) the CD matrix CD1_1,CD1_2... plus CRPIX and CRVAL.   
;       (3) CROTA2 (or CROTA1) and CDELT plus CRPIX and CRVAL.
;
;       All three forms are valid FITS according to the paper "Representations 
;       of World Coordinates in FITS by Greisen and Calabretta (2002, A&A, 395,
;       1061 http://fits.gsfc.nasa.gov/fits_wcs.html ) although form (1) is 
;       preferred.
;
; NOTES:
;       1.  An anonymous structure is created to avoid structure definition
;       conflicts.    This is needed because some projection systems
;       require additional dimensions (i.e. spherical cube
;       projections require a specification of the cube face).
;
;       2,  FITS headers created by SCAMP 
;      (http://www.astromatic.net/software/scamp) contain a tangent 
;       projection with distortion polynomial coefficients in the PV[1|2]_?
;       keywords.    These will be flagged as being a TPV projection 
;       (http://fits.gsfc.nasa.gov/registry/tpvwcs.html) in the 
;       astr.projection keyword.    
;           
; PROCEDURES CALLED:
;      GSSSEXTAST, ZPARCHECK
; REVISION HISTORY
;      Written by B. Boothman 4/15/86
;      Accept CD001001 keywords               1-3-88
;      Accept CD1_1, CD2_1... keywords    W. Landsman    Nov. 92
;      Recognize GSSS FITS header         W. Landsman    June 94
;      Get correct sign, when converting CDELT* to CD matrix for right-handed
;      coordinate system                  W. Landsman   November 1998
;      Consistent conversion between CROTA and CD matrix  October 2000
;      CTYPE = 'PIXEL' means no astrometry params  W. Landsman January 2001
;      Don't choke if only 1 CTYPE value given W. Landsman  August 2001
;      Recognize PC00n00m keywords again (sigh...)  W. Landsman December 2001
;      Recognize GSSS in ctype also       D. Finkbeiner Jan 2002
;      Introduce ALT keyword              W. Landsman June 2003
;      Fix error introduced June 2003 where free-format values would be
;      truncated if more than 20 characters.  W. Landsman Aug 2003
;      Further fix to free-format values -- slash need not be present Sep 2003
;      Default value of LATPOLE is 90.0  W. Landsman February 2004
;      Allow for distortion substructure, currently implemented only for
;          SIP (Spitzer Imaging Polynomial)   W. Landsman February 2004 
;      Correct LONGPOLE computation if CTYPE = ['*DEC','*RA'] W. L. Feb. 2004
;      Assume since V5.3 (vector STRMID)  W. Landsman Feb 2004
;      Yet another fix to free-format values   W. Landsman April 2004
;      Introduce PV2 tag to replace PROJP1, PROJP2.. etc.  W. Landsman May 2004
;      Convert NCP projection to generalized SIN   W. Landsman Aug 2004
;      Add NAXIS tag to output structure  W. Landsman Jan 2007
;      .CRPIX tag now Double instead of Float   W. Landsman  Apr 2007
;      If duplicate keywords use the *last* value W. Landsman Aug 2008
;      Fix typo for AZP projection, nonzero longpole N. Cunningham Feb 2009
;      Give warning if reverse SIP coefficient not present  W. Landsman Nov 2011
;      Allow obsolete CD matrix representations W. Landsman May 2012
;      Work for Paritel headers with extra quotes R. Gutermuth/WL  April 2013
;
;	Version 2:  J. P. Leahy, July 2013
;		- Support long & lat axes not being the first 2.
;		- Implemented PV1 and hence non-default phi0 and theta0
;		- Added AXES, REVERSE, COORD_SYS, PROJECTION, RADECSYS, EQUINOX,
;			DATEOBS, MJDOBS, PV1, and X0Y0 tags to the structure.
;		- More checks for inconsistencies in the keywords.
;	v2.1 21/7/13 Missing mjdobs & equinox changed to NaN (was -1 & 0);
;		Converts GLS to SFL if possible; added KNOWN tag.
;	v2.2 21/9/13 GLS conversion fixed.
;	v2.3 1 Dec 13 Add warning if distortions from SCAMP astrometry present
;	v2.4.  Extract SCAMP or TPV distortion astrometry, if present Jan 2014
;	v2.5  Fix bug when SIP parameters not recognized when NAXIS=0 May 2014
;	v2.5.1 Make sure CROTA defined for GLS projection WL Sep 2015
;	v2.5.2 Like V2.5.1 but also when CD matrix suppied WL May 2016
;	v2.5.3 Add warning if CPDIS1 keyword present WL Nov 2016
;	v2.5.4 Add HAS_CPDIS and HAS_D2IMDIS keywords WL Nov 2017
;-

 compile_opt idl2
 ;
 ; List of known map types copied from wcsxy2sph. Needs to be kept up
 ; to date!
 ;
 map_types=['DEF','AZP','TAN','SIN','STG','ARC','ZPN','ZEA','AIR','CYP',$
            'CAR','MER','CEA','COP','COD','COE','COO','BON','PCO','SFL',$
            'PAR','AIT','MOL','CSC','QSC','TSC','SZP','HPX','HCT','XPH']

 if ( N_params() LT 2 ) then begin
     print,'Syntax - EXTAST, hdr, astr, [ noparams, ALT =, HAS_CPDIS= ]'
     return
 endif
 
 Catch, theError
 IF theError NE 0 then begin
     Catch,/Cancel
     void = cgErrorMsg(/quiet)
     RETURN
     ENDIF                                                      

 radeg = 180.0D0/!DPI
 keyword = STRUPCASE(strtrim(strmid( hdr, 0, 8), 2))

; Extract values from the FITS header.   This is either up to the first slash
; (free format) or first space

 space = strpos( hdr, ' ', 10) + 1
 slash = strpos( hdr, '/', 10)  > space
 
 N = N_elements(hdr)
 len = (slash -10) > 20
 len = reform(len,1,N)
 lvalue = strtrim(strmid(hdr, 10, len),2)
 remchar,lvalue,"'"
 zparcheck,'EXTAST',hdr,1,7,1,'FITS image header'   ;Make sure valid header
 noparams = -1                                    ;Assume no astrometry to start

 if N_elements(alt) EQ 0 then begin
     alt = '' & altstr = ''
 endif else BEGIN
     if (alt EQ '1') then alt = 'A' else alt = strupcase(alt)
     altstr = ' for alternate system '+alt
 ENDELSE
 
 ; Search for astrometric axes:
 test = STREGEX(keyword,'^CTYPE[1-9][0-9]{0,2}'+alt+'$', LENGTH = ctlen)
 typ = WHERE(test GE 0, ntyp)
 lon = -1  & lat = -1
 lon_form = -1 & lat_form = -1
 
 IF ntyp GT 0 THEN BEGIN
     ctlen = ctlen[typ] - STRLEN('CTYPE'+alt) ; gives # digits in axis number
     
     lon0 = WHERE(STRMID(lvalue[typ],0,5) EQ 'RA---')
     lon1 = WHERE(STRMID(lvalue[typ],1,4) EQ  'LON-')
     lon2 = WHERE(STRMID(lvalue[typ],2,4) EQ   'LN-')
     lon = [lon0, lon1, lon2]
     form = [REPLICATE(0,N_ELEMENTS(lon0)),REPLICATE(1,N_ELEMENTS(lon1)), $
             REPLICATE(2,N_ELEMENTS(lon2))]
     good = WHERE(lon GT 0, ngood)
     IF ngood GT 1 THEN MESSAGE, /INFORMATIONAL, $
                  'Multiple longitude axes'+altstr+': Using last.'
     lon = MAX(lon, subs)
     lon_form = lon GE 0 ? form[subs] : -1

     lat0 = WHERE(STRMID(lvalue[typ],0,5) EQ 'DEC--')
     lat1 = WHERE(STRMID(lvalue[typ],1,4) EQ  'LAT-')
     lat2 = WHERE(STRMID(lvalue[typ],2,4) EQ   'LT-')
     lat = [lat0, lat1, lat2]
     form = [REPLICATE(0,N_ELEMENTS(lat0)),REPLICATE(1,N_ELEMENTS(lat1)), $
             REPLICATE(2,N_ELEMENTS(lat2))]
     good = WHERE(lat GT 0, ngood)
     IF ngood GT 1 THEN MESSAGE, /INFORMATIONAL, $
                  'Multiple latitude axes'+altstr+': Using last.'
     lat = MAX(lat,subs)
     lat_form = lat GE 0 ? form[subs] : -1
 ENDIF
 
;
; Longitude axis data is initially stored in element 0 and latitude
; axis data in element 1 of the various arrays. For backwards compatibility,
; if latitude has a lower axis number in the FITS header, they will be swapped 
; into the (latitude, longitude) order in the final structure, and the .REVERSE 
; field will be set to true (ie. 1B).
; 
 lonc = lon GE 0 ? STRMID(keyword[typ[lon]],5,ctlen[lon]) : '1'
 latc = lat GE 0 ? STRMID(keyword[typ[lat]],5,ctlen[lat]) : '2'

 ctype = ['','']
 l = where(keyword EQ 'CTYPE'+lonc+alt,  N_ctype1)
 if N_ctype1 GT 0 then ctype[0] = lvalue[l[N_ctype1-1]]
 l = where(keyword EQ 'CTYPE'+latc+alt,  N_ctype2)
 if N_ctype2 GT 0 then ctype[1] = lvalue[l[N_ctype2-1]]
 ctype = strtrim(ctype,2)


 badco = lon_form NE lat_form 
 CASE lon_form OF
     -1: coord = 'X'  ; unknown type of coordinate
      0: coord = 'C'  ; celestial coords, i.e. RA/Dec
      1: BEGIN  ; longitude format is xLON where x = G, E, etc.
          coord = STRMID(ctype[0],0,1)
          badco = badco || coord NE STRMID(ctype[1],0,1)
      END
      2: BEGIN  ; longitude format is yzLN 
          coord = STRMID(ctype[0],0,2)
          badco = badco || coord NE STRMID(ctype[2],0,2)
      END
      ELSE: MESSAGE, 'Internal error: unexpected lon_form' 
 ENDCASE

 naxis = lonarr(2)
 l = where(keyword EQ 'NAXIS'+lonc,  N_axis1)
 if N_axis1 GT 0 then naxis[0] = lvalue[l[N_axis1-1]]
 l = where(keyword EQ 'NAXIS'+latc,  N_axis2)
 if N_axis2 GT 0 then naxis[1] = lvalue[l[N_axis2-1]]

 tpv = strmid(ctype[0],2,3,/reverse) EQ 'TPV'
 tnx = strmid(ctype[0],2,3,/reverse) EQ 'TNX'
 
 IF (TPV || tnx) THEN BEGIN 
    proj = 'TAN'
 ENDIF ELSE BEGIN
 proj = STRMID(ctype[0], 5, 3)
 
 badco = badco || proj NE STRMID(ctype[1], 5, 3)
 IF badco THEN BEGIN
     MESSAGE, 'ERROR' + altstr + $
      ': longitude and latitude coordinate types must match:', /CONTINUE
     MESSAGE, 'Coords were CTYPE'+lonc+alt+': ' + ctype[0] + $
                        '; CTYPE'+latc+alt+': ' + ctype[1]
 ENDIF 
  
; If the standard CTYPE* astrometry keywords not found, then check if the
; ST guidestar astrometry is present

 check_gsss = (N_ctype1 EQ 0)
 if N_ctype1 GE 1  then check_gsss = (strmid(ctype[0], 5, 3) EQ 'GSS')

 if check_gsss then begin

        l = where(keyword EQ 'PPO1'+alt,  N_ppo1)
        if N_ppo1 EQ 1 then begin 
                gsssextast, hdr, astr, gsssparams
                if gsssparams EQ 0 then noparams = 4
                return
        endif
 endif

 if (ctype[0] EQ 'PIXEL') then return
 if N_ctype2 EQ 1 then if (ctype[1] EQ 'PIXEL') then return
 ENDELSE

 crval = dblarr(2)

 l = where(keyword EQ 'CRVAL'+lonc+alt,  N_crval1)
 if N_crval1 GT 0 then crval[0] = lvalue[l[N_crval1-1]]
 l = where(keyword EQ 'CRVAL'+latc+alt,  N_crval2)
 if N_crval2 GT 0 then crval[1] = lvalue[l[N_crval2-1]]
 if (N_crval1 EQ 0) || (N_crval2 EQ 0) then return  

 crpix = dblarr(2)
 l = where(keyword EQ 'CRPIX'+lonc+alt,  N_crpix1)
 if N_crpix1 GT 0 then crpix[0] = lvalue[l[N_crpix1-1]]
 l = where(keyword EQ 'CRPIX'+latc+alt,  N_crpix2)
 if N_crpix2 GT 0 then crpix[1] = lvalue[l[N_crpix2-1]]
 if (N_crpix1 EQ 0) || (N_crpix2 EQ 0) then return  


 cd = dblarr(2,2)
 cdelt = [1.0d,1.0d]

; Look for distortion keywords pointing to another FITS extension
 
 cpdis1 = ''
 l = where(keyword EQ 'CPDIS1',  N_cpdis1)
 if N_cpdis1 GT 0 then cpdis1 = strtrim(lvalue[l[N_cpdis1-1]],2)
 haslookup =  strupcase(cpdis1) EQ 'LOOKUP'
 if arg_present(has_cpdis) then has_cpdis = haslookup else begin 
	if haslookup then begin 
	    message, /inf, $
		'Warning - FITS header may point to table lookup distortions (CPDIS1)'
		 message,/inf, 'Use FITS_XYAD or FITS_ADXY to include distortion lookup table'
    endif
   endelse
 
 d2imdis1 = ''  
 l = where(keyword EQ 'D2IMDIS1',  N_d2imdis1)
 if N_d2imdis1 GT 0 then d2imdis1 = strtrim(lvalue[l[N_cpdis1-1]],2)
 haslookup =  strupcase(d2imdis1) EQ 'LOOKUP'
 if arg_present(has_d2imdis) then has_d2imdis =haslookup else begin 
	if haslookup then begin 
	    message, /inf, $
		'Warning - FITS header may point to table lookup distortions (D2IMDIS1)'
		 message,/inf, 'Use FITS_XYAD or FITS_ADXY to include distortion lookup table'
    endif
   endelse
   
GET_CD_MATRIX:

 l = where(keyword EQ 'PC'+lonc+'_'+lonc + alt,  N_pc11) 
 if N_PC11 GT 0 then begin 
        cd[0,0]  = lvalue[l]
        l = where(keyword EQ 'PC'+lonc+'_'+latc + alt,  N_pc12) 
        if N_pc12 GT 0 then cd[0,1]  = lvalue[l[N_pc12-1]]
        l = where(keyword EQ 'PC'+latc+'_'+lonc + alt,  N_pc21) 
        if N_pc21 GT 0 then cd[1,0]  = lvalue[l[N_pc21-1]]
        l = where(keyword EQ 'PC'+latc+'_'+latc + alt,  N_pc22) 
        if N_pc22 GT 0 then cd[1,1]  = lvalue[l[N_pc22-1]]
         l = where(keyword EQ 'CDELT'+lonc+ alt,  N_cdelt1) 
        if N_cdelt1 GT 0 then cdelt[0]  = lvalue[l[N_cdelt1-1]]
         l = where(keyword EQ 'CDELT'+latc+ alt,  N_cdelt2) 
        if N_cdelt2 GT 0 then cdelt[1]  = lvalue[l[N_cdelt2-1]]
        det = cd[0,0]*cd[1,1] - cd[0,1]*cd[1,0]
        if det LT 0 then sgn = -1 else sgn = 1
        crota  = atan(  sgn*cd[0,1],  sgn*cd[0,0] ) 
        noparams = 3
 endif else begin 

    l = where(keyword EQ 'CD'+lonc+'_'+lonc + alt,  N_cd11) 
    if N_CD11 GT 0 then begin        ;If CD parameters don't exist, try CROTA
        cd[0,0]  = strtrim(lvalue[l[N_cd11-1]],2)
        l = where(keyword EQ 'CD'+lonc+'_'+latc + alt,  N_cd12) 
        if N_cd12 GT 0 then cd[0,1]  = lvalue[l[N_cd12-1]]
        l = where(keyword EQ 'CD'+latc+'_'+lonc + alt,  N_cd21) 
        if N_cd21 GT 0 then cd[1,0]  = lvalue[l[N_cd21-1]]
        l = where(keyword EQ 'CD'+latc+'_'+latc + alt,  N_cd22) 
        if N_cd22 GT 0 then cd[1,1]  = lvalue[l[N_cd22-1]]
        det = cd[0,0]*cd[1,1] - cd[0,1]*cd[1,0]
        if det LT 0 then sgn = -1 else sgn = 1
        crota  = atan(  sgn*cd[0,1],  sgn*cd[0,0] ) 
        noparams = 2
    endif else begin

; Now get rotation, first try CROTA2, if not found try CROTA1, if that
; not found assume North-up.   Then convert to CD matrix - see Section 5 in
; Greisen and Calabretta

        l = where(keyword EQ 'CDELT'+lonc + alt,  N_cdelt1) 
        if N_cdelt1 GT 0 then cdelt[0]  = lvalue[l[N_cdelt1-1]]
        l = where(keyword EQ 'CDELT'+latc + alt,  N_cdelt2) 
        if N_cdelt2 GT 0 then cdelt[1]  = lvalue[l[N_cdelt2-1]]
        if (N_cdelt1 EQ 0) || (N_Cdelt2 EQ 0) then return   
                                           ;Must have CDELT1 and CDELT2

        l = where(keyword EQ 'CROTA'+latc + alt,  N_crota) 
        if N_Crota EQ 0 then $
            l = where(keyword EQ 'CROTA'+lonc + alt,  N_crota) 
        if N_crota EQ 0 then begin
	          l = where(keyword EQ 'PC001001', N_PC00)
	          l = where(keyword EQ 'CD001001', N_CD00)
	          if (N_PC00 GT 0) || (N_CD00 GT 0) then begin
	              message,'Updating obsolete CD matrix representation',/INF
		            FITS_CD_FIX, hdr
		            keyword = strtrim(strmid(hdr,0,8),2)
		            goto, GET_CD_MATRIX
	          endif else crota = 0.0d 
	      endif else crota = double(lvalue[l[N_crota-1]])/RADEG
        cd = [ [cos(crota), -sin(crota)],[sin(crota), cos(crota)] ] 
 
        noparams = 1           ;Signal AIPS-type astrometry found
     
    endelse
 endelse

; Kluge to test for non-standard PVi_j distortion terms used by SCAMP
 scamp_distort = 0b
 if ~tpv && (proj EQ 'TAN')  then $
     tpv = ~array_equal(strmatch(keyword,'PV1_[5-9]'),0) &&  $  ;Updated 1-8-14
           ~array_equal(strmatch(keyword,'PV2_[3-9]'),0)   
	  
;Extract PV_* keywords.   Special case for TPV distortion
 if tpv then begin
   g= where(strmatch(keyword,'PV1_*'), Ng)
   key_pv1 = keyword[g]
   temp =  gettok(key_pv1,'_')
   key_pv1 = fix(key_pv1)      
   pv1 = dblarr(max(key_pv1)+1)
   pv1[key_pv1] = lvalue[g]

   g= where(strmatch(keyword,'PV2_*'), Ng)
   key_pv2 = keyword[g]
   temp =  gettok(key_pv2,'_')
   key_pv2 = fix(key_pv2)      
   pv2 = dblarr(max(key_pv2)+1)       ;Corrected 13-Jan-2014
   pv2[key_pv2] = lvalue[g]
   
   latpole = 90.0D
   longpole = 180.0D
   known = 1.0
   x0y0 = [0d0, 0d0]
   distort_flag = 'TPV' 
ENDIF ELSE BEGIN
   ;; extract the tnx coefficients from the WAT keywords
  
   IF(tnx)THEN BEGIN
      g=where(strmatch(keyword,'WAT1_*'),Ng)
      key_wat1=keyword[g]
      val_wat1=STRTRIM(strmid(hdr[g], 10),2)
      remchar,val_wat1,"'"
      remchar,val_wat1,'"'
      remchar,val_wat1,'/'
      temp=STRMID(key_wat1,0,3,/REVERSE)
      s=SORT(temp)
      val_wat1=val_wat1[s]
      val_wat1=STRJOIN(val_wat1)
      val_wat1=STRSPLIT(val_wat1,/EXTRACT)
      
      g=where(strmatch(keyword,'WAT2_*'),Ng)
      key_wat2=keyword[g]
      val_wat2=STRTRIM(strmid(hdr[g], 10),2)
      remchar,val_wat2,"'"
      remchar,val_wat2,'"'
      remchar,val_wat2,'/'
      temp=STRMID(key_wat2,0,3,/REVERSE)
      s=SORT(temp)
      val_wat2=val_wat2[s]
      val_wat2=STRJOIN(val_wat2)
      val_wat2=STRSPLIT(val_wat2,/EXTRACT)
      IF(val_wat1[2] NE 'lngcor' || val_wat2[2] NE 'latcor')THEN BEGIN
         MESSAGE,'WARNING: TNX projection parameters not parsed correctly',/CON
         ctype = ['RA---TAN','DEC--TAN']
         tnx=0
      ENDIF
      IF(val_wat1[4] NE 3 || val_wat2[4] NE 3)THEN BEGIN
         MESSAGE,'WARNING - only polynomials supported for TNX projection.',/CON
         ctype = ['RA---TAN','DEC--TAN']
         tnx=0
      ENDIF
    
      IF(tnx)THEN BEGIN
         ;; tnx coefficients get stored in two structures
         ncoeff=N_ELEMENTS(val_wat1)-12
         lngcor={functype:0,xiorder:0,etaorder:0,xterms:0,ximin:0d0,ximax:0d0,etamin:0d0,etamax:0d0,coeff:DBLARR(ncoeff)}
         lngcor.functype=FIX(val_wat1[4])
         lngcor.xiorder=FIX(val_wat1[5])
         lngcor.etaorder=FIX(val_wat1[6])
         lngcor.xterms=FIX(val_wat1[7])
         lngcor.ximin=DOUBLE(val_wat1[8])
         lngcor.ximax=DOUBLE(val_wat1[9])
         lngcor.etamin=DOUBLE(val_wat1[10])
         lngcor.etamax=DOUBLE(val_wat1[11])
         lngcor.coeff=DOUBLE(val_wat1[12:*])
         
         ncoeff=N_ELEMENTS(val_wat2)-12
         latcor={functype:0,xiorder:0,etaorder:0,xterms:0,ximin:0d0,ximax:0d0,etamin:0d0,etamax:0d0,coeff:DBLARR(ncoeff)}
         latcor.functype=FIX(val_wat2[4])
         latcor.xiorder=FIX(val_wat2[5])
         latcor.etaorder=FIX(val_wat2[6])
         latcor.xterms=FIX(val_wat2[7])
         latcor.ximin=DOUBLE(val_wat2[8])
         latcor.ximax=DOUBLE(val_wat2[9])
         latcor.etamin=DOUBLE(val_wat2[10])
         latcor.etamax=DOUBLE(val_wat2[11])
         latcor.coeff=DOUBLE(val_wat2[12:*])
         distort_flag = 'TNX'
      ENDIF ELSE distort_flag=''
   ENDIF ELSE BEGIN
      distort_flag = strlen(ctype[0]) GE 12 ? strmid(ctype[0],9,3) : ''   
   ENDELSE
   case proj of 
     'ZPN': npv = 21
     'SZP': npv = 3
     else:  npv = 2
 endcase
 
 index = proj EQ 'ZPN' ? strtrim(indgen(npv),2) : strtrim(indgen(npv)+1,2)     
 pv2 = dblarr(npv)
 if proj EQ 'HPX' then pv2[0] = [4.d,3.d]    ;Default for Healpix

 for i=0,npv-1 do begin 
     l = where(keyword EQ 'PV'+latc+ '_' + index[i] + alt,  N_pv2)
     if N_pv2 GT 0 then pv2[i] = lvalue[l[N_pv2-1]] 
 endfor
 
 pv1 = DBLARR(5)
 pv1_set = BYTARR(5)
 FOR i=0,4 DO BEGIN
     l = WHERE(keyword EQ 'PV'+lonc+'_' + STRTRIM(i,2) + alt, N_pv1)
     pv1_set[i] = N_pv1 GT 0
     IF pv1_set[i] THEN pv1[i] = DOUBLE(lvalue[l[N_pv1-1]])
 ENDFOR
 xyoff = pv1[0] NE 0d0
 phi0  = pv1[1]
 if pv1_set[2] THEN theta0 = pv1[2]
 if pv1_set[3] then longpole = pv1[3] else begin
     l = where(keyword EQ 'LONPOLE' + alt,  N_lonpole)
     if N_lonpole GT 0 then  longpole = double(lvalue[l[N_lonpole-1]]) 
 endelse
 if pv1_set[4] then latpole = pv1[4] else begin
     l = where(keyword EQ 'LATPOLE' + alt,  N_latpole)
     latpole = N_latpole GT 0 ? double(lvalue[l[N_latpole-1]]) : 90d0 
 endelse
 
; Convert NCP projection to generalized SIN projection (see Section 6.1.2 of 
; Calabretta and Greisen (2002)

 if proj EQ 'NCP' then begin
     ctype = repstr(ctype,'NCP','SIN')
     proj = 'SIN'
     PV2 = [0d0, 1d0/tan(crval[1]/radeg) ]
     longpole = 180d0
 endif 
 
; Convert GLS projection (Sect 6.1.4, ibid), but per e-mail from Mark
; Calabretta the correction to CRPIX and CRVAL should only be applied
; to the second axis.
 IF proj EQ 'GLS' THEN BEGIN
     IF crota EQ 0d0 THEN BEGIN
         crpix[1] -= crval[1]/cdelt[1] ; Shift reference point to dec = 0
         crval[1] = 0d0
         ctype = repstr(ctype,'GLS','SFL')
         proj = 'SFL'
     ENDIF
 ENDIF 

 test = WHERE(proj EQ map_types)
 known = test GE 0
 
 ; If LONPOLE (or PV1_3) is not defined in the header, then we must determine 
; its default value.    This depends on the value of theta0 (the native
; longitude of the fiducial point) of the particular projection)

 conic = (proj EQ 'COP') || (proj EQ 'COE') || (proj EQ 'COD') || $
         (proj EQ 'COO')

 IF conic THEN BEGIN 
     IF N_pv2 EQ 0 THEN message, $
     'ERROR -- Conic projections require a PV2_1 keyword in FITS header'
     theta_a = pv2[0]
 ENDIF ELSE BEGIN ; Is it a zenithal projection?
     if (proj EQ 'AZP') || (proj EQ 'SZP') || (proj EQ 'TAN') || $
        (proj EQ 'STG') || (proj EQ 'SIN') || (proj EQ 'ARC') || $
        (proj EQ 'ZPN') || (proj EQ 'ZEA') || (proj EQ 'AIR') || $
        (proj EQ 'XPH') then begin
         theta_a = 90d0
     endif else theta_a = 0d0
 ENDELSE
     
 IF ~pv1_set[2] THEN BEGIN
     theta0 = theta_a 
     pv1[2] = theta_a
 ENDIF
 
 if N_elements(longpole) EQ 0 then begin 
     if crval[1] GE theta0 then longpole = 0d0 else longpole = 180d0
     if pv1_set[1] THEN longpole += phi0
 endif
 
 pv1[3] = longpole
 pv1[4] = latpole


 IF xyoff && (phi0 NE 0d0 || theta0 NE theta_a) THEN BEGIN 
 ; calculate IWC offsets x_0, y_0
     WCSSPH2XY, phi0, theta0, x0, y0, CTYPE = ctype, PV2 = pv2
     x0y0 = [x0, y0]
 ENDIF ELSE x0y0 = [0d0, 0d0]
ENDELSE
   
 axes = FIX([lonc,latc])
 flip = axes[0] GT axes[1]
 IF flip THEN BEGIN
     naxis = REVERSE(naxis)
     axes  = REVERSE(axes)
     cdelt = REVERSE(cdelt)
     crpix = REVERSE(crpix)
     crval = REVERSE(crval)
     ctype = REVERSE(ctype)
     cd    = ROTATE(cd,2)
     x0y0  = REVERSE(x0y0)
 ENDIF
 
 equinox = GET_EQUINOX( hdr,eq_code, ALT = alt) 
 IF equinox EQ 0 THEN equinox = !values.D_NAN
 radecsys = ''
 mjdobs  = !values.D_NAN
 dateobs = 'UNKNOWN'
 l = WHERE(keyword EQ 'RADESYS' + alt,  N_rdsys)
 IF N_rdsys GT 0 THEN radecsys = lvalue[l[N_rdsys-1]] ELSE BEGIN
     l = WHERE(keyword EQ 'RADECSYS',  N_rdsys)
     IF N_rdsys GT 0 THEN radecsys = lvalue[l[N_rdsys-1]]
 ENDELSE
 IF N_rdsys GT 0 THEN radecsys = STRUPCASE(STRTRIM(radecsys,2))
 
 l = WHERE(keyword EQ 'MJD-OBS',  N_mjd)
 IF N_mjd GT 0 THEN mjdobs = DOUBLE(lvalue[l[N_mjd-1]])
 l = WHERE(keyword EQ 'DATE-OBS',  N_date)
 IF N_date GT 0 THEN dateobs = STRUPCASE(lvalue[l[N_date-1]])
 
 IF N_mjd GT 0 && N_date EQ 0 THEN dateobs = date_conv(mjdobs+2400000.5d0,'FITS')
 IF N_date GT 0 THEN BEGIN
                            ; try to convert to standard format:
     dateobs = date_conv(dateobs,'FITS', BAD_DATE=bad_date) 
     IF ~bad_date THEN BEGIN
        mjdtest = date_conv(dateobs,'MODIFIED')
        IF N_mjd EQ 0 THEN mjdobs = mjdtest ELSE $
            IF ABS(mjdtest - mjdobs) GT 1 THEN MESSAGE, $
               'DATE-OBS and MJD-OBS are inconsistent'
     ENDIF ELSE dateobs = 'UNKNOWN'
 ENDIF

 IF (coord EQ 'C' || coord EQ 'E' || coord EQ 'H') THEN BEGIN
     IF N_rdsys EQ 0 THEN CASE eq_code OF
        -1: radecsys = 'ICRS' ; default if no header info.
         0: radecsys = equinox GE 1984d0 ? 'FK5' : 'FK4'
         1: radecsys = equinox GE 1984d0 ? 'FK5' : 'FK4'
         2: radecsys = 'FK4'
         3: radecsys = 'FK5'
         4: ; shouldn't get here as implies radecsys exists.
         else: MESSAGE, 'Internal error: unrecognised eq_code'
     ENDCASE
 ENDIF
 
; Note that the dimensions and datatype of each tag must be explicit, so that
; there is no conflict with structure definitions from different FITS headers
 
 ASTR = {NAXIS:naxis, CD: cd, CDELT: cdelt, CRPIX: crpix, CRVAL: crval, $
         CTYPE: string(ctype), $
         LONGPOLE: double( longpole[0]),  LATPOLE: double(latpole[0]), $
         PV2: pv2, PV1: pv1, $
         AXES: axes, REVERSE: flip, $
         COORD_SYS: coord, PROJECTION: proj, KNOWN: known, $
         RADECSYS: radecsys, EQUINOX: DOUBLE(equinox), $
         DATEOBS: dateobs, MJDOBS: DOUBLE(mjdobs), X0Y0: x0y0}

; Check for any distortion keywords

 
     case distort_flag of 
         'SIP': begin
             l = where(keyword EQ 'A_ORDER',  N) 
             if N GT 0 then a_order  = lvalue[l[N-1]] else a_order = 0
             l = where(keyword EQ 'B_ORDER',  N) 
             if N GT 0 then b_order  = lvalue[l[N-1]] else b_order = 0
             l = where(keyword EQ 'AP_ORDER',  N) 
             if N GT 0 then ap_order  = lvalue[l[N-1]] else ap_order = 0
             l = where(keyword EQ 'BP_ORDER',  N) 
             if N GT 0 then bp_order  = lvalue[l[N-1]] else bp_order = 0
             a = dblarr(a_order+1,a_order+1)
             b = dblarr(b_order+1,b_order+1) 
             ap = dblarr(ap_order+1,ap_order+1)
             bp = dblarr(bp_order+1,bp_order+1)

             for i=0, a_order do begin
                 for j=0, a_order do begin
             l = where(keyword EQ 'A_' + strtrim(i,2) + '_' + strtrim(j,2), N)
             if N GT 0 then a[i,j] = lvalue[l[N-1]]
                 endfor
             endfor

             for i=0, b_order  do begin
                 for j=0, b_order do begin
             l = where(keyword EQ 'B_' + strtrim(i,2) + '_' + strtrim(j,2), N)
             if N GT 0 then b[i,j] = lvalue[l[N-1]]
                 endfor
             endfor

             for i=0, bp_order do begin
                 for j=0, bp_order do begin
             l = where(keyword EQ 'BP_' + strtrim(i,2) + '_' + strtrim(j,2), N)
             if N GT 0 then bp[i,j] = lvalue[l[N-1]]
                 endfor
             endfor

             for i=0, ap_order do begin
                 for j=0, ap_order do begin
             l = where(keyword EQ 'AP_' + strtrim(i,2) + '_' + strtrim(j,2), N)
             if N GT 0 then ap[i,j] = lvalue[l[N-1]]
                 endfor
             endfor
   
             distort = {name:distort_flag, a:a, b:b, ap:ap, bp:bp}
             astr = create_struct(temporary(astr), 'distort', distort)
          end
          'TPV': begin 
             distort = {name:'TPV', a:0.0d, b:0.0d, ap:0.0d, bp:0.0d}
             astr = create_struct(temporary(astr), 'distort', distort)
          end
          'TNX' : begin
             distort = {name:'TNX', lngcor:lngcor, latcor:latcor}
             astr = create_struct(temporary(astr), 'distort', distort)
          end
       '': 	 
     else: message,/con,'Unrecognized distortion acronym: ' + distort_flag 
 endcase
 
 return
 end