<!-- A template for a DaCHS SSAP (spectral search) service.
Since spectra usually come in crazy formats, this already
contains a datalink service so you'll usually serve proper SDM VOTables.

To fill it out, search and replace %.*%

Note that this doesn't expose all features of DaCHS.  For advanced
projects, you'll still have to read documentation...
\tpldesc{Spectra via SSAP and TAP, going through datalink}
-->

<resource schema="\resdir" resdir=".">
  <meta name="creationDate">\now</meta>

  \commonmeta

   <meta name="ssap.dataSource">%one of survey, pointed, theory, custom, artificial)%</meta>
  <meta name="ssap.creationType">%one of archival, cutout, filtered, mosaic, projection, spectralExtraction, catalogExtraction%</meta>
  <!-- in case you serve anything but a spectrum, see
  http://www.ivoa.net/rdf/product-type
  for what terms you can use here; you can have more than one productType -->
  <meta name="productType">spectrum</meta>
  <meta name="ssap.testQuery">MAXREC=1</meta>

  <table id="raw_data" onDisk="True" adql="hidden"
      namePath="//ssap#instance">
    <!-- the table with your custom metadata; it is transformed
      to something palatable for SSA using the view below -->

    <!-- for an explanation of what columns will be defined in the
    final view, see http://docs.g-vo.org/DaCHS/ref.html#the-ssap-view-mixin.

    Don't mention anything constant here; fill it in in the view
    definition.
    -->
    <LOOP listItems="%adapt this list to what metadata actually varies among your data sets.%
      ssa_dateObs ssa_dstitle ssa_targname ssa_length
      ssa_specres ssa_timeExt">
      <events>
        <column original="\\item"/>
      </events>
    </LOOP>

    <mixin>//products#table</mixin>
    <!-- remove this if your data doesn't have (usable) positions -->
    <mixin>//ssap#plainlocation</mixin>
    <!-- remove this if you don't have plainlocation or there is no
      aperture -->
    <mixin>//ssap#simpleCoverage</mixin>
    <!-- the following adds a q3c index so obscore queries over s_ra
      and s_dec are fast; again, remove this if you don't have useful
      positions -->
    <FEED source="//scs#splitPosIndex"
      columns="ssa_location"
      long="degrees(long(ssa_location))"
      lat="degrees(lat(ssa_location))"/>

    <!-- the datalink column is mainly useful if you have a form-based
      service.  You can dump this (and the mapping rule filling it below)
      if you're not planning on web or don't care about giving them datalink
      access. -->
    <column name="datalink" type="text"
      ucd="meta.ref.url"
      tablehead="Datalink"
      description="A link to a datalink document for this spectrum."
      verbLevel="15" displayHint="type=url">
      <property name="targetType"
       >application/x-votable+xml;content=datalink</property>
      <property name="targetTitle">Datalink</property>
    </column>

    %add further custom columns if necessary here%
  </table>

  <!-- if you have data that is continually added to, consider using
    updating="True" and an ignorePattern here; see also howDoI.html,
    incremental updating.  -->
  <data id="import">
<!-- uncomment later   <recreateAfter>make_view</recreateAfter> -->
    <property key="previewDir">previews</property>
    <sources recurse="True"
      pattern="%resdir-relative pattern, like data/*.fits%"/>

    <!-- The following (and the datalink stuff) assumes you have
      IRAF-style 1D arrays; if you have something even more flamboyant,
      you'll have to change things here as well as in build_sdm_data;
      you'll want to keep the products#define rowmaker, though. -->
    <fitsProdGrammar qnd="True">
      <rowfilter procDef="//products#define">
        <bind key="table">"\\schema.raw_data"</bind>
        <!-- make the following a macro expansion once you have
        commented in the sdl service below -->
        <bind key="path">"fullDLURL{sdl}"</bind>
        <!-- the next item should be estimated (the stuff will
          be generated on the fly).  Make it something like
          10000+20*number of data points or so. -->
        <bind key="fsize">%typical size of an SDM VOTable in bytes%</bind>
        <bind key="mime">"application/x-votable+xml"</bind>
        <bind key="preview">\\standardPreviewPath</bind>
        <bind key="preview_mime">"image/png"</bind>
      </rowfilter>
    </fitsProdGrammar>

    <make table="raw_data">
      <rowmaker idmaps="*">
        <!-- put vars here to pre-process FITS keys that you need to
          re-format in non-trivial ways. -->

        <var key="specAx">%use getWCSAxis(@header_, 1) for IRAF FITSes (delete otherwise)%</var>

        <apply procDef="//ssap#fill-plainlocation">
          <bind key="ra">%where to get the RA from%</bind>
          <bind key="dec">%where to get the Dec from%</bind>
          <bind key="aperture">%the aperture (a constant estimate may just do it)%</bind>
        </apply>

        <!-- the following maps assume the column list in the LOOP
          above.  If you changed things there, you'll have to adapt
          things here, too -->
        <map key="ssa_dateObs">dateTimeToMJD(parseTimestamp(@%key for dateObs%,
          "%Y-%m-%d %H:%M:%S"))</map>
        <map key="ssa_dstitle">"{} {}".format(%make a string halfway human-readable and halfway unique for each data set%)</map>
        <map key="ssa_targname">%which header to get the target name from%</map>
        <map key="ssa_specstart">%typically, @specAx.pixToPhys(1)*1e-10%</map>
        <map key="ssa_specend"
            >%typically, @specAx.pixToPhys(@specAx.axisLength)*1e-10%</map>
        <map key="ssa_length">%typically, @specAx.axisLength%</map>
        <map key="ssa_timeExt">%where to get an exposure time from%</map>
        <map key="ssa_specres">%expression to compute the typical spectral FWHM *in meters*%</map>

<!-- uncomment later        <map key="datalink">\\dlMetaURI{sdl}</map> -->

        <!-- add mappings for your own custom columns here. -->
      </rowmaker>
    </make>
  </data>


<macDef raw="True" name="todo"><![CDATA[

  <table id="data" onDisk="True" adql="True">
    <!-- the SSA table (on which the service is based) -->

    <!-- <meta name="_associatedDatalinkService">
      <meta name="serviceId">sdl</meta>
      <meta name="idColumn">ssa_pubDID</meta>
    </meta> -->

    <!-- again, the full list of things you can pass to the mixin
      is at http://docs.g-vo.org/DaCHS/ref.html#the-ssap-view-mixin.

      Things you already defined in raw_data are ignored here; you
      can also (almost always) leave them out altogether here.
      Defaulted attributes (the doc has "defaults to" for them) you
      can remove.

      The values for the ssa_ attributes below are SQL expressions – that
      is, you need to put strings in single quotes.
    -->
    <mixin
      sourcetable="raw_data"
      copiedcolumns="*"
      ssa_aperture="1/3600."
      ssa_fluxunit="'%Flux unit in the spectrum instance. Use'' for uncalibrated data.%'"
      ssa_spectralunit="'%Unit used in the spectrum itself ('Angstrom' or so)%'"
      ssa_bandpass="'%something like Optical or K; don't sweat it%'"
      ssa_collection="'%a very terse designation for your dataset%'"
      ssa_fluxcalib="'%ABSOLUTE|CALIBRATED|RELATIVE|NORMALIZED|UNCALIBRATED%'"
      ssa_fluxucd="%'phot.flux.density;em.wl', quite likely%"
      ssa_speccalib="'%ABSOLUTE|CALIBRATED|RELATIVE|NORMALIZED|UNCALIBRATED%'"
      ssa_spectralucd="'%em.wl;obs.atmos or similar for optical spectra%'"
      ssa_targclass="'%e.g., star; use Simbad object types%'"
    >//ssap#view</mixin>

		<mixin
		  calibLevel="%likely one of 1 for uncalibrated or 2 for calibrated data%"
		  coverage="%ssa_region -- or remove this if you have no ssa_region%"
		  sResolution="ssa_spaceres"
		  oUCD="ssa_fluxucd"
		  emUCD="ssa_spectralucd"
		  >//obscore#publishSSAPMIXC</mixin>
  </table>

  <data id="make_view" auto="False">
    <make table="data"/>
  </data>

  <coverage>
    <updater sourceTable="data"/>
  </coverage>

  <!-- This is the table definition *for a single spectrum* as used
    by datalink.  If you have per-bin errors or whatever else, just
    add columns as above. -->
  <table id="instance" onDisk="False">
    <mixin ssaTable="data"
      spectralDescription="%something like 'Wavelength' or so%"
      fluxDescription="%something like 'Flux density' or so%"
      >//ssap#sdm-instance</mixin>
    <meta name="description">%a few words what a spectrum represents%</meta>
  </table>


  <!-- this data item build spectrum *instances* (for datalink) -->
  <data id="build_spectrum" auto="False">
    <embeddedGrammar>
      <iterator>
        <setup imports="gavo.protocols.products,gavo.utils.pyfits"/>
        <code>
          # we receive a pubDID in self.sourceToken["ssa_pubDID"];
          # the physical accref is behind its "?", potentially URL-encoded.

          sourcePath = os.path.join(
            base.getConfig("inputsDir"),
            urllib.parse.unquote(
              self.sourceToken["ssa_pubDID"].split('?', 1)[-1]))

          % add code returning rowdicts from sourcePath %
          # In the common case of 1D FITS images repurposed a a spectrum
          # this could be something like
          # fitsPath = products.RAccref.fromString(
					#	self.sourceToken["accref"]).localpath
				  # hdus = pyfits.open(fitsPath)
				  # ax = utils.getWCSAxis(hdus[0].header, 1)

				  # for spec, flux in enumerate(hdus[0].data):
					#   yield {"spectral": ax.pix0ToPhys(spec), "flux": flux}
				  # hdus.close()
        </code>
      </iterator>
    </embeddedGrammar>
    <make table="instance">
      <parmaker>
        <apply procDef="//ssap#feedSSAToSDM"/>
      </parmaker>
    </make>
  </data>

  <!-- the datalink service spitting out SDM spectra (and other formats
    on request) -->
  <service id="sdl" allowed="dlget,dlmeta">
    <meta name="title">\\schema Datalink Service</meta>
    <datalinkCore>
      <descriptorGenerator procDef="//soda#sdm_genDesc">
        <bind key="ssaTD">"\\rdId#data"</bind>
      </descriptorGenerator>
      <dataFunction procDef="//soda#sdm_genData">
        <bind key="builder">"\\rdId#build_spectrum"</bind>
      </dataFunction>
      <FEED source="//soda#sdm_plainfluxcalib"/>
      <FEED source="//soda#sdm_cutout"/>
      <FEED source="//soda#sdm_format"/>

      <!-- give people an option to retrieve the ugly FITS, too;
        again, adapt or drop if you have something else -->
   		<metaMaker semantics="#progenitor">
				<code>
					if descriptor.pubDID is None:
						return
					# this assumes normal accref computation: the original file
					# then is just inputsDir/accref
					yield descriptor.makeLinkFromFile(
						base.getConfig("inputsDir") / descriptor.accref,
						description="Spectrum as the original 1D array")
				</code>
			</metaMaker>

    </datalinkCore>
  </service>

  <!-- a form-based service – this is made totally separate from the
  SSA part because grinding down SSA to something human-consumable and
  still working as SSA is non-trivial -->
  <service id="web" defaultRenderer="form">
    <meta name="shortName">\\schema Web</meta>

    <dbCore queriedTable="data">
      <condDesc buildFrom="ssa_location"/>
      <condDesc buildFrom="ssa_dateObs"/>
      <!-- add further condDescs in this pattern; if you have useful target
      names, you'll probably want to index them and say:

      <condDesc>
        <inputKey original="data.ssa_targname" tablehead="Standard Stars">
          <values fromdb="ssa_targname from \\schema.data
            order by ssa_targname"/>
        </inputKey>
      </condDesc> -->
    </dbCore>

    <outputTable>
      <autoCols>accref, mime, ssa_targname,
        ssa_aperture, ssa_dateObs, datalink</autoCols>
      <FEED source="//ssap#atomicCoords"/>
      <outputField original="ssa_specstart" displayHint="spectralUnit=Angstrom"/>
      <outputField original="ssa_specend" displayHint="spectralUnit=Angstrom"/>
    </outputTable>
  </service>

  <service id="ssa" allowed="form,ssap.xml">
    <meta name="shortName">\\schema SSAP</meta>
    <meta name="ssap.complianceLevel">full</meta>

    <publish render="ssap.xml" sets="ivo_managed"/>
    <publish render="form" sets="ivo_managed,local" service="web"/>

    <ssapCore queriedTable="data">
      <property key="previews">auto%delete this line if you have no previews; else delete just this.%</property>
      <FEED source="//ssap#hcd_condDescs"/>
    </ssapCore>
  </service>

  <regSuite title="\resdir regression">
    <!-- see http://docs.g-vo.org/DaCHS/ref.html#regression-testing
      for more info on these. -->

    <regTest title="\resdir SSAP serves some data">
      <url REQUEST="queryData" PUBDID="%a value you have in ssa_pubDID%"
        >ssa/ssap.xml</url>
      <code>
        <!-- to figure out some good strings to use here, run
          dachs test -k SSAP -D tmp.xml q
          and look at tmp.xml -->
        self.assertHasStrings(
          "%some characteristic string returned by the query%",
          "%another characteristic string returned by the query%")
      </code>
    </regTest>

    <regTest title="\resdir Datalink metadata looks about right.">
      <url ID="%a value you have in ssa_pubDID%"
        >sdl/dlmeta</url>
      <code>
        <!-- to figure out good items to test here, you probably want to
          dachs test -k datalink  q
          and pprint the by_sem dict -->
          by_sem = self.datalinkBySemantics()
          print(by_sem)
          self.fail("Fill this in")
      </code>
    </regTest>

    <regTest title="\resdir delivers some data.">
      <url ID="%a value you have in ssa_pubDID%"
        >sdl/dlget</url>
      <code>
        <!-- to figure out some good strings to use here, run
          dachs test -k "delivers data" -D tmp.xml q
          and look at tmp.xml -->
        self.assertHasStrings(
          "%some characteristic string in the datalink meta%")
      </code>
    </regTest>

    <!-- add more tests: form-based service renders custom widgets, etc. -->
  </regSuite>
  ]]></macDef>
</resource>
