
FUNCTION eis_obs_structure, t0, t1, study_acr=study_acr, count=count, rast_acr=rast_acr, $
                            tl_id_input=tl_id_input, quiet=quiet, rast_id=rast_id, $
                            quick=quick

;+
; NAME:
;      EIS_OBS_STRUCTURE
;
; PURPOSE:
;      Creates a structure containing information about all EIS
;      rasters run during the specified time period T0 to T1.
;
; CATEGORY:
;      Hinode/EIS; metadata
;
; INPUTS:
;      T0:   Start time of period in a standard format accepted by the
;            ANYTIM routines. E.g., '1-feb-2011 00:00'.
;      T1:   End time of period in a standard format accepted by the
;            ANYTIM routines. E.g., '2-feb-2011 00:00'.
;
; OPTIONAL INPUTS:
;      Study_acr: Filters the output based on the study acronym. Can
;                 be an array, and string case does not matter. A
;                 partial match is OK, but the match must begin with
;                 the first character.
;      Tl_id_input: This specifies a particular timeline ID (tl_id),
;                 and only results for this tl_id will be returned.
;      Rast_acr:  Filters the output on the raster acronym.
;      Rast_id:   Filters the output on the raster ID.
;
; KEYWORD PARAMETERS:
;      QUIET:  If set, then no informational messages will be printed
;              to screen.
;      QUICK:  If set, then the routine does not query the Oslo catalog,
;              which speeds up the routine but means the xcen_corr and
;              ycen_corr tags are not filled.
;
; OPTIONAL OUTPUTS:
;      Count:   Number of rasters satisfying search criteria.
;
; OUTPUT:
;      A structure array with various tags giving information about
;      the rasters that were run. If no entries are found, then a
;      value of -1 is returned.
;
;      Please check notes below about xcen and ycen values. Generally
;      you should use the xcen_corr and ycen_corr values.
;
;      Some of the tags are:
;       .nexp  No. of exposures in an exposure set.
;       .exp_times String containing the exposure times for the set.
;       .nsteps No. of exposure sets. For a raster, this is the x-size
;               of the raster. For sit-and-stare it is the number of
;               repeats at the slit position.
;
; PROGRAMMING NOTES:
;      XCEN_CORR, YCEN_CORR
;        These are keywords stored at the OSDC
;        (http://sdc.uio.no/search/form) that contain the best xcen and
;        ycen values. That is, they are derived from the getxcen and
;        getycen object methods, and then they are corrected for the
;        AIA-EIS offset (eis_aia_offsets.pro). An internet connection
;        is required to retrieve these values. If you find the values are
;        NaNs, then this means the routine was not successful in
;        retrieving the values.
;        
;      XCEN_PLAN, YCEN_PLAN
;        Sometimes the xcen and ycen values for a raster have very
;        large errors. In this case it is preferable to use the
;        *planned* coordinates, and these are stored in the xcen_plan
;        and ycen_plan tags. *However*, if there are raster repeats
;        then xcen_plan and ycen_plan are the coordinates of the *first*
;        raster in the sequence.
;
;      RAST_ACR, STUDY_ACR
;        If you're filtering the search based on the
;        raster/study acronym, then it is much quicker to filter on
;        the raster acronym due to how the databases are
;        structured. Generally there's a one-to-one
;        correspondence between the raster and study acronyms, but
;        usually the acronyms are different. To find
;        the raster acronym for a study, use the routine EIS_XSTUDY,
;        find your study, and you should see the raster acronym
;        towards the bottom of the widget. Note that you can search on
;        the study and raster acronym together.
;
;      Exposure times (NEXP, EXP_TIMES, NSTEPS)
;        For a given slit position, EIS can take up to 8 exposures, and
;        these exposures can have different exposure times. An "exposure
;        set" denotes the set of exposures at the slit position. The tag
;        NEXP gives the number of exposures in a set. Usually there is
;        only one exposure (NEXP=1), but sometimes EIS may take short
;        and long exposures to prevent saturation in strong lines
;        (NEXP=2).
;
;        The exposure times for the set are given in the tag EXP_TIMES.
;        This is a string, with the exposure times separated by a space.
;        For example, NEXP=2 and EXP_TIMES="5.0 50.0" means there are
;        two exposures in the set of 5 s and 50 s duration.
;
;        The number of exposure sets in a raster is given by NSTEPS. For
;        a spatial raster, this is the x-size of the raster. For
;        sit-and-stare observations, this is the number of repeated
;        exposure sets. Thus if NSTEPS=30 and NEXP=2, then there were
;        60 exposures in total.
;
; EXAMPLES:
;      IDL> s=eis_obs_structure('1-jan-2014','1-feb-2014')
;      IDL> s=eis_obs_structure('1-jan-2014','1-jan-2015',study_acr='Atlas_30')
;
;      The example below shows how different the runtime can be if
;      rast_acr is used:
;      IDL> s=eis_obs_structure('15-jan-2007','20-aug-2010',study_acr='pry_footpoints')
;      IDL> s=eis_obs_structure('15-jan-2007','20-aug-2010',study_acr='pry_footpoints', $
;              rast_acr='cool_lines')
;      The first one takes 1-2 minutes, the second takes a few seconds.
;
; MODIFICATION HISTORY:
;      Version 1, 2-Apr-2012, Peter Young
;      Version 2, 16-Jul-2012, Peter Young
;         added obstitle, obs_dec, sci_obj and hop_id to the output
;         structure. 
;      Version 3, 4-Jan-2013, Peter Young
;         added STUDY_ACR optional input; fixed bug that led to
;         incorrect rast_acr and rast_id tags; added COUNT= optional
;         output. 
;      Version 4, 10-Apr-2014, Peter Young
;         added some additional tags to the output structure.
;      Version 5, 4-Aug-2014, Peter Young
;         added search for tl_id.
;      Version 6, 3-Apr-2015, Peter Young
;         now filters out entries with bad date information.
;      Version 7, 10-Nov-2015, Peter Young
;         now remove any entries prior to 23-Nov-2006 13:14 as these
;         have bad meta-data; also make sure date_end > date_obs.
;      Version 8, 18-Nov-2015, Peter Young
;         I've added the tags 'xcen_plan' and 'ycen_plan' which
;         are the planned coordinates of the raster.
;      Version 9, 19-Nov-2015, Peter Young
;         Added 'offset_index' tag to output.
;      Version 10, 13-Sep-2019, Peter Young
;         Fixed problem with count output not working in some cases;
;         updated header format.
;      Version 11, 6-Dec-2019, Peter Young
;         Fixed bug with rast_acr search - it is much faster now;
;         moved the check on study_acr to improve speed; added some
;         informational messages about speed.
;      Version 12, 17-Jan-2020, Peter Young
;         Removed engineering studies from output by checking
;         rast_acr.
;      Version 13, 01-Apr-2020, Peter Young
;         Added 'atlas' tag to denote if raster is a spectral atlas.
;      Version 14, 02-Apr-2020, Peter Young
;         Fixed bug when /quiet set.
;      Version 15, 05-Feb-2021, Peter Young
;         The input rast_acr was being redefined in the code,
;         so I've changed the internal parameter to
;         rast_acr_out; the t0 and t1 parameters that were used for
;         timing code have been changed to tt0 and tt1.
;      Version 16, 08-Feb-2021, Peter Young
;         Fixed bug whereby I was still computing the durations with
;         t1-t0 instead ot tt1-tt0.
;      Version 17, 04-Oct-2021, Peter Young
;         Fixed directory problem that stopped routine working in
;         Windows; removed the NRL thumbnail link since the site does
;         not exist now.
;      Version 18, 16-Dec-2021, Peter Young
;         Added yws to output structure.
;      Version 19, 03-Feb-2022, Peter Young
;         Added rast_id optional input.
;      Version 20, 23-Aug-2022, Peter Young
;         Modified check on study_acr so that a partial match is allowed.
;      Version 21, 25-Aug-2023, Peter Young
;         Added the tags xcen_corr and ycen_corr to the output. These
;         contain the xcen_corr and ycen_corr keywords from the OSDC,
;         which contain the most accurate pointings for the rasters.
;      Version 22, 28-Aug-2023, Peter Young
;         The previous update didn't work if the tl_id option was used,
;         so this has been fixed.
;      Version 23, 12-Sep-2023, Peter Young
;         Fixed bug in calculation of NSTEPS for sit-and-stare data;
;         added extra information to header.
;      Version 24, 13-Sep-2023, Peter Young
;         Now handles case when xcen_corr and ycen_corr are both zero.
;      Version 25, 14-Sep-2023, Peter Young
;         Added /quick keyword.
;-


IF n_params() LT 2 AND n_elements(rast_acr) EQ 0 AND n_elements(rast_id) EQ 0 AND n_elements(tl_id_input) EQ 0 THEN BEGIN
  print,'Use either'
  print,'      IDL> obs=eis_obs_structure( t0, t1 [, study_acr=, count=, rast_acr=, rast_id= ])'
  print,'or'
  print,'      IDL> obs=eis_obs_structure( rast_acr= [, t0, t1, study_acr=, count= ])'
  print,'or'
  print,'      IDL> obs=eis_obs_structure( rast_id= [, t0, t1, study_acr=, count= ])'
  print,'or'
  print,'      IDL> obs=eis_obs_structure( tl_id= )'
  count=0
  return,-1
ENDIF 


IF n_elements(study_acr) NE 0 AND n_elements(rast_acr) EQ 0 THEN BEGIN
  print,'% EIS_OBS_STRUCTURE: *WARNING* generally it is much quicker to search on the raster acronym (RAST_ACR) rather'
  print,'                     than the study acronym. See the header for more information.'
ENDIF 

;
; The following give the paths to the EIS databases used by this
; routine. The databases are:
;
;  dbexpt - The "as-run" raster database
;  dbmain - The "as-run" study database
;  dbrast - The raster technical database
;  dbline - The line list technical database
;  dbtlras - The "as-planned" raster database
;
dbase_dir=concat_dir(getenv('SSW'),'hinode')
dbase_dir=concat_dir(dbase_dir,'eis')
dbase_dir=concat_dir(dbase_dir,'database')
cat_dir=concat_dir(dbase_dir,'catalog')
plan_dir=concat_dir(dbase_dir,'planning_db')
tech_dir=concat_dir(plan_dir,'technical_db')
tl_dir=concat_dir(plan_dir,'timeline_db')
;
dbexpt=concat_dir(cat_dir,'eis_experiment')
dbmain=concat_dir(cat_dir,'eis_main')
dbrast=concat_dir(tech_dir,'eis_raster_db')
dbline=concat_dir(tech_dir,'eis_linelist_db')
dbtlras=concat_dir(tl_dir,'eis_science2_db')


;
; Construct time query
;
IF n_elements(t0) NE 0 AND n_elements(t1) NE 0 THEN BEGIN 
  t0_tai=anytim2tai(t0)
  t0_tai_str=trim(t0_tai,'(f15.3)')
  t1_tai=anytim2tai(t1)
  t1_tai_str=trim(t1_tai,'(f15.3)')
 ;
  query='date_obs > '+t0_tai_str+', date_end < '+t1_tai_str
ENDIF ELSE BEGIN
  query=''
ENDELSE



;
; Construct raster acronym query
;
nra=n_elements(rast_acr)
IF nra NE 0 THEN BEGIN
  IF nra EQ 1 THEN BEGIN
    r_query='rast_acr = '+rast_acr[0]
  ENDIF ELSE BEGIN
    r_query='rast_acr = ['
    FOR i=0,n_elements(rast_acr)-1 DO BEGIN
      r_query=r_query+trim(rast_acr[i])
      IF i EQ n_elements(rast_acr)-1 THEN r_query=r_query+']' ELSE  r_query=r_query+','
    ENDFOR
  ENDELSE 
  IF query EQ '' THEN query=r_query ELSE query=query+', '+r_query
ENDIF

;
; Construct raster ID query
;
nra=n_elements(rast_id)
IF nra NE 0 THEN BEGIN
  IF nra EQ 1 THEN BEGIN
    r_query='rast_id = '+trim(rast_id[0])
  ENDIF ELSE BEGIN
    r_query='rast_id = ['
    FOR i=0,n_elements(rast_id)-1 DO BEGIN
      r_query=r_query+trim(rast_id[i])
      IF i EQ n_elements(rast_id)-1 THEN r_query=r_query+']' ELSE  r_query=r_query+','
    ENDFOR
  ENDELSE 
  IF query EQ '' THEN query=r_query ELSE query=query+', '+r_query
  rast_id_save=rast_id
ENDIF



;
; Timeline ID (tl_id) query.
;
IF n_elements(tl_id_input) NE 0 THEN BEGIN
  t_query='tl_id = '+trim(tl_id_input)
  IF query EQ '' THEN query=t_query ELSE query=query+', '+t_query
ENDIF 


IF query EQ '' THEN BEGIN
  print,'%EIS_OBS_STRUCTURE: either a time range T0,T1 should be specified, or RAST_ACR should be specified.'
  print,'                    Returning...'
  count=0
  return, -1
ENDIF 

;
; Extract information from the as-run EIS raster database
;
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: querying as-run raster database...'
tt0=systime(1)
dbopen,dbexpt
list=dbfind(query,/silent)
dbext,list,'xcen,ycen,fovx,fovy,filename,tl_id,date_obs,date_end,rast_acr,rast_id,yws', $
      xcen,ycen,fovx,fovy,filename,tl_id,date_obs,date_end,rast_acr_out,rast_id,yws
dbclose
tt1=systime(1)
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: query took '+trim(string(format='(f15.1)',tt1-tt0))+' seconds.'

;
; Sort output arrays according to observation time
;
k=sort(date_obs)
date_obs=tai2utc(double(date_obs[k]),/ccsds)
date_end=tai2utc(double(date_end[k]),/ccsds)
xcen=xcen[k]
ycen=ycen[k]
fovx=fovx[k]
fovy=fovy[k]
filename=filename[k]
tl_id=tl_id[k]
rast_acr_out=rast_acr_out[k]
rast_id=rast_id[k]
yws=yws[k]

;
; Create the output observation structure
; 
n=n_elements(date_obs)
str={ date_obs: '', date_end: '', xcen: 0., ycen: 0., xcen_plan: 0., ycen_plan: 0., $
      xcen_corr: !values.f_nan, ycen_corr: !values.f_nan, $
      fovx: 0., fovy: 0., $
      filename: '', tl_id: 0l, stud_acr: '', rast_acr: '', link: '', rast_id: 0, $
      wavelnth:'', wavemin: '', wavemax: '', obstitle: '', obs_dec: '', $
      hop_id: 0, sci_obj: '', slit_width: 0, sit_stare: 0b, step_size: 0., $
      slit_index: 0, nsteps: 0, nexp: 0, exp_times: '', offset_index: 0, $
      atlas: 0b, yws: 0}
struct=replicate(str,n)

;
; Populate the structure with items from the raster database.
;
struct.date_obs=date_obs
struct.date_end=date_end
struct.xcen=xcen
struct.ycen=ycen
struct.fovx=fovx
struct.fovy=fovy
struct.filename=trim(str_replace(filename,'.gz',''))
struct.tl_id=tl_id
struct.rast_acr=trim(rast_acr_out)
struct.rast_id=rast_id
struct.yws=yws


;
; If the tl_id option was used, then t0 and t1 will not be defined,
; so set them here. (Needed for OSDC call.)
;
IF n_elements(t0) EQ 0 THEN BEGIN
  t0=struct[0].date_obs
  t1=struct[n-1].date_end
ENDIF 

;
; 25-Aug-2023
; The ycen values have become very inaccurate, so I now query the OSDC to
; return xcen_corr and ycen_corr values.
;
IF NOT keyword_set(quick) THEN BEGIN
 ;
 ; By specifying the raster ID(s) in the OSDC search, it should make the
 ; search faster.
 ;
  rast_id=struct.rast_id
  ra_id=rast_id[uniq(rast_id,sort(rast_id))]
  nr=n_elements(ra_id)
  ra_id_str=trim(ra_id[0])
  IF nr GT 1 THEN BEGIN 
    FOR i=1,nr-1 DO ra_id_str=ra_id_str+','+trim(ra_id[i])
  ENDIF 
 ;
  t_osdc_0=systime(1)
  IF NOT keyword_set(quiet) THEN message,/info,/CONTINUE,'Querying OSDC to return xcen_corr and ycen_corr. This can be turned off with /QUICK.'
  o=obj_new('osdc',/quiet)
  o->show,'DATE_OBS,XCEN_CORR,YCEN_CORR'
  o->limit,0
  o->condition,'INSTRUME: ,EIS'
  o->condition,'EPOCH_START: '+t0
  o->condition,'EPOCH_END: '+t1
  o->condition,'SS__L1LEAD: y'
  o->condition,'Gx: NONE'
  o->condition,'E__RAST_ID: '+ra_id_str
  o->search,osdc_data
  obj_destroy,o
  t_osdc_1=systime(1)
  IF NOT keyword_set(quiet) THEN message,/info,/CONTINUE,'OSDC query took '+trim(string(format='(f6.1)',t_osdc_1-t_osdc_0))+' seconds.'
ENDIF
;
; Sometimes xcen_corr and ycen_corr come back as zero from the OSDC. This
; seems to be because the FITS files aren't available, usually because the
; files are new. I thus retain NaNs for these files. 
;
IF n_tags(osdc_data) NE 0 THEN BEGIN 
  osdc_date_obs_tai=anytim2tai(osdc_data.date_obs)
  FOR i=0,n-1 DO BEGIN
    date_obs_tai=anytim2tai(struct[i].date_obs)
    getmin=min(abs(osdc_date_obs_tai-date_obs_tai),imin)
    IF getmin LE 1.0 THEN BEGIN
      osdc_xcen_corr=osdc_data[imin].xcen_corr
      osdc_ycen_corr=osdc_data[imin].ycen_corr
      IF osdc_xcen_corr NE 0. OR osdc_ycen_corr NE 0. THEN BEGIN 
        struct[i].xcen_corr=osdc_xcen_corr
        struct[i].ycen_corr=osdc_ycen_corr
      ENDIF 
    ENDIF 
  ENDFOR
ENDIF 


;
; When no rasters are found, the code always seems to put an empty
; entry with a date corresponding to 1957 (!), so here I check for
; this and delete it.
;
; 10-Nov-2015: I've modified this to check for date_obs after
; 23-Nov-2006 13:13. This (I believe) is when the EIS planning tool
; started working, and so entries before this have bad meta-data. I
; also added a check for date_end > date_obs. There's one study
; in particular that fails this.
;
; 17-Jan-2020: I now check to make sure rast_acr is not 'NA' as this
; indicates an engineering study. (I think a negative rast_id is also a
; flag, but I'm not sure of this.)
;
date_obs_tai=anytim2tai(date_obs)
date_end_tai=anytim2tai(date_end)
rast_acr_out=struct.rast_acr
chck_tai=anytim2tai('23-nov-2006 13:13')
k=where(date_obs_tai GE chck_tai AND date_end_tai GE date_obs_tai AND rast_acr_out NE 'NA',nk)
IF nk EQ 0 THEN BEGIN
  IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: no studies found. Returning...'
  count=0
  return,-1
ENDIF ELSE BEGIN
  struct=struct[k]
ENDELSE 

;
; Create a link to the NRL thumbnail webpage.
;
; 04-Oct-2021, PRY: commented this out since NRL site no longer
; exists.
;
;struct.link='http://tcrb.nrl.navy.mil/~iuu/eis/thumbnails/rasters/'+strlowcase(trim(struct.rast_acr))+'/'+strlowcase(trim(struct.filename))+'.html'

;
; Extract the study acronym from the as-run study database.
; (note that ">" means "greater-or-equal-to" for the database software)
;
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: querying as-run study database...'
tt0=systime(1)
dbopen,dbmain
query='tl_id > '+trim(min(tl_id))+', tl_id < '+trim(max(tl_id))
list=dbfind(query,/silent)
dbext,list,'stud_acr,tl_id,obstitle,obs_dec,sci_obj,jop_id,obs_num',stud_acr,st_tl_id, $
      obstitle,obs_dec,sci_obj,hop_id,obs_num
dbclose
tt1=systime(1)
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: query took '+trim(string(format='(f15.1)',tt1-tt0))+' seconds.'

;
; Extract the xcen and ycen values from the *as-planned*
; database. These go in the xcen_plan and ycen_plan tags of the output
; structure.
; Note that tl_id in the as-planned raster database is identified as
; 'sn_id'. 
;
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: querying as-planned raster database...'
tt0=systime(1)
dbopen,dbtlras
query='sn_id > '+trim(min(tl_id))+', sn_id < '+trim(max(tl_id))
list=dbfind(query,/silent)
dbext,list,'sn_id,x_point,y_point',tl_tl_id,xcen_plan,ycen_plan
dbclose
tt1=systime(1)
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: query took '+trim(string(format='(f15.1)',tt1-tt0))+' seconds.'

;
; Populate the stud_acr tag of the observation structure.
;
; For obstitle, obs_dec and sci_obj, the string returned by the
; database software has 68 characters, with the last character being a
; "&". I don't think there's a fix for this (Ron's software
; returns the strings in this same format). I'm thus just
; truncating the string to 67 characters to remove the &.
;
n=n_elements(stud_acr)
FOR i=0,n-1 DO BEGIN
  k=where(struct.tl_id EQ st_tl_id[i],nk)
  IF nk NE 0 THEN BEGIN
    struct[k].stud_acr=trim(stud_acr[i])
    struct[k].obstitle=trim(strmid(obstitle[i],0,67))
    struct[k].obs_dec=trim(strmid(obs_dec[i],0,67))
    struct[k].sci_obj=trim(strmid(sci_obj[i],0,67))
    struct[k].hop_id=hop_id[i]
    struct[k].offset_index=obs_num[i]
  ENDIF
ENDFOR 


;
; The following loads up the tags 'xcen_plan' and 'ycen_plan' using
; information from the as-planned database (dbtlras).
;
n=n_elements(tl_tl_id)
FOR i=0,n-1 DO BEGIN
  k=where(struct.tl_id EQ tl_tl_id[i],nk)
  IF nk NE 0 THEN BEGIN
    struct[k].xcen_plan=xcen_plan[i]
    struct[k].ycen_plan=ycen_plan[i]
  ENDIF 
ENDFOR 


;
; Perform a filter using the STUDY_ACR input.
;
IF n_elements(study_acr) NE 0 THEN BEGIN
  nn=n_elements(study_acr)
  ind=-1
  FOR i=0,nn-1 DO BEGIN
   ;
   ; I'm checking to see if the study acronym begins with the string contained in study_acr.
   ; A partial match is OK, but the match must begin with the first character.
   ;
    chck=strpos(strlowcase(struct.stud_acr),trim(strlowcase(study_acr[i])))
    k=where(chck EQ 0,nk)   
;    k=where(trim(strlowcase(struct.stud_acr)) EQ trim(strlowcase(study_acr[i])),nk)
    IF nk NE 0 THEN ind=[ind,k]
  ENDFOR 
  IF n_elements(ind) GT 1 THEN BEGIN
    ind=ind[1:*]
    struct=struct[ind]
  ENDIF ELSE BEGIN
    struct=-1
  ENDELSE 
ENDIF 
;
IF n_tags(struct) EQ 0 THEN BEGIN
  count=0
  IF n_elements(rast_id_save) NE 0 THEN rast_id=rast_id_save
  return,struct
ENDIF ELSE BEGIN
  count=n_elements(struct)
ENDELSE 


slit_width=[1,266,2,40]

;
; Extract information about the raster wavelength windows using the
; line list and raster definition databases.
;
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: extracting line list and raster information...'
tt0=systime(1)
;
FOR i=min(struct.rast_id),max(struct.rast_id) DO BEGIN
  k=where(i EQ struct.rast_id,nk)
  IF nk GT 0 THEN BEGIN
    dbopen,dbrast
    query='id = '+trim(i)
    list=dbfind(query,/silent)
    dbext,list,'ll_id,n_windows,data_windows,rastertype,slit_index,scan_fm_nsteps,scan_fm_stepsize,nexp,exposures,sns_nexps', $
          ll_id,n_windows,data_windows,rastertype,slit_index,scan_fm_nsteps, $
          scan_fm_stepsize,nexp,exposures,sns_nexps
    n_windows=n_windows[0]
    dbclose,dbrast
   ;
    dbopen,dbline
    query='id = '+trim(ll_id)
    list=dbfind(query,/silent)
    dbext,list,'n_lines,wavelength',n_lines,wavelength
    n_lines=n_lines[0]
    dbclose,dbline
   ;
    IF n_lines GT 0 THEN BEGIN
     ;
     ; Check if raster is an atlas (4 windows, each of 1024 pix)
     ;
      IF n_lines EQ 4 AND min(data_windows[0:n_lines-1]) EQ 1024 AND max(data_windows[0:n_lines-1]) EQ 1024 THEN struct[k].atlas=1b
     ;
      wavemin=lonarr(n_lines)
      wavemax=lonarr(n_lines)
      FOR j=0,n_lines-1 DO BEGIN
        wavemin[j]=fix((float(wavelength[j])/100.-data_windows[j]/2.*0.0223)*100.)
        wavemax[j]=fix((float(wavelength[j])/100.+data_windows[j]/2.*0.0223)*100.)
      ENDFOR 
     ;
      wave_str=trim(wavelength[0])
      wavemin_str=trim(wavemin[0])
      wavemax_str=trim(wavemax[0])
      IF n_lines GT 1 THEN BEGIN 
        FOR j=1,n_lines-1 DO BEGIN
          wave_str=wave_str+' '+trim(wavelength[j])
          wavemin_str=wavemin_str+' '+trim(wavemin[j])
          wavemax_str=wavemax_str+' '+trim(wavemax[j])
        ENDFOR 
      ENDIF 
      struct[k].wavelnth=wave_str
      struct[k].wavemin=wavemin_str
      struct[k].wavemax=wavemax_str
    ENDIF 
   ;
    struct[k].sit_stare=rastertype[0]
    struct[k].slit_width=slit_width[slit_index[0]]
    struct[k].slit_index=slit_index[0]
   ;
   ; For sit-and-stare observations, nsteps is the number of exposure sets and
   ; is obtained from sns_nexps
   ;
    IF rastertype[0] EQ 1 THEN BEGIN
      struct[k].nsteps=sns_nexps[0]
    ENDIF ELSE BEGIN 
      struct[k].nsteps=scan_fm_nsteps[0]
    ENDELSE 

    IF rastertype[0] NE 1 THEN struct[k].step_size=scan_fm_stepsize[0]
    nexp=nexp[0]
    struct[k].nexp=nexp
    exps=exposures[0:nexp-1]
    exp_str=''
    FOR ii=0,nexp-1 DO exp_str=exp_str+' '+trim(string(format='(f10.1)',float(exps[ii])/1000.))
    exp_str=trim(exp_str)
    struct[k].exp_times=exp_str
  ENDIF 
ENDFOR
tt1=systime(1)
IF NOT keyword_set(quiet) THEN print,'% EIS_OBS_STRUCTURE: completed in '+trim(string(format='(f15.1)',tt1-tt0))+' seconds.'

;
; Restores the original input value to rast_id.
;
IF n_elements(rast_id_save) NE 0 THEN rast_id=rast_id_save
  
return,struct

END

