<resource schema="califadr3" resdir="califa">
	<meta name="creationDate">2016-04-04T08:34:00</meta>
	<meta name="schema-rank">20</meta>
	<meta name="title">CALIFA (Calar Alto Legacy Integral Field
		spectroscopy Area) survey DR3</meta>
	<meta name="creator">Sánchez, F.; The CALIFA collaboration</meta>
	<meta name="version">3</meta>

	<meta name="subject">disk-galaxies</meta>
	<meta name="subject">interstellar-medium</meta>
	<meta name="subject">spectroscopy</meta>

	<meta name="facility">Calar Alto Observatory</meta>
	<meta name="instrument">PMAS/PPAK at 3.5m Telescope</meta>
	<meta name="source">2016A&amp;A...594A..36S</meta>

	<meta name="coverage.waveband">Optical</meta>
	<meta name="content.type">Survey</meta>

	<meta name="description" format="rst">
		The Calar Alto Legacy Integral Field Area (CALIFA) survey provides
		spatially resolved spectroscopic information for 667 galaxies, mainly
		within the local universe (0.005 &lt; z &lt; 0.03).
		
		CALIFA data was obtained using the PPAK integral field unit (IFU), with a
		hexagonal field-of-view of 1.3 square arcmin, with a 100% covering factor
		by adopting a three-pointing dithering scheme.  has been taken in two
		setups: V500 (6 Å bin size, 646 galaxies) and V1200 (2.3 Å bin size, 484
		galaxies).  A final product ("COMBO") combining both data sets, covering
		3700-7500 Å at 6 Å bin size, is made availble for 484 galaxies.
		
		CALIFA is a legacy survey, intended for the community.  This is the (final)
		Data Release 3.  This is the VO/TAP publication with a simple web
		interface on top.  See http://califa.caha.es/ for the upstream
		site.</meta>

	<meta name="copyright" format="rst">
		CALIFA asks you to acknowledge:

		"This study uses data provided by the Calar Alto Legacy Integral Field Area
		(CALIFA) survey (http://califa.caha.es/)."

		"Based on observations collected at the Centro Astronómico Hispano Alemán
		(CAHA) at Calar Alto, operated jointly by the Max-Planck-Institut fűr
		Astronomie and the Instituto de Astrofísica de Andalucía (CSIC)."

		and to cite both of :bibcode:`2014A&amp;A...569A...1W` and
		:bibcode:`2012A&amp;A...538A...8S`
	</meta>

	<meta name="_longdoc" format="rst"><![CDATA[
A TAP example
=============

The CALIFA cubes are available in database tables at this site.  Here's an
example for how to solve a little problem using VO tools.

There are two additional examples for how to use TAP to explore the CALIFA
data in our TAP examples:

* `Query for CALIFA object properties`_
* `Make a color map from CALIFA cubes`_

.. _Query for CALIFA object properties: http://dc.g-vo.org/tap/examples#Queryforcalifaobjectproperties
.. _Make a color map from CALIFA cubes: http://dc.g-vo.org/tap/examples#MakeacolormapfromCALIFAcubes

Problem
-------

The idea is to put all cubes at the rest frame. So if you have the velocity
recession in the header (MED_VEL; or taken from NED), just have to do::

	w_rest = w_obs/(1 + (recvel/c_speed))

Once I have a common wavelength reference, I want to look if I have a
certain emission line. Let's say I look for Halpha. An easy way to do it
without measuring the line is define a continuum an compare the fluxes. So
if::

	flux_6563 / [(flux_6540+flux_6700)/2.0]  > 3

it's a hint of having Halpha emission. As you see, I use to continuum points
at both sides of the line.

Then, I would ask for all spectra in the cubes having this condition.

Solution
--------

There are several ways to solve this problem using VO tools and
services, but we will, somewhat unorthodoxly, use TAP, a protocol to
exchanges SQL (actually, ADQL_) queries and query results with servers.
There are several clients that can do this; if you have no other
preferences, use TOPCAT_.  There, open VO/TAP, push the pin in the left
upper corner of the window (to keep the window open after sending off a
query), and in the TAP URL field below, enter http://dc.g-vo.org/tap.
Hit "Enter query", and you are in a dialog that lets you inspect a
server's table metadata (look for tables in califadr3) and enter
queries.

The targets of the CALIFA survey and their redshifts are in the
`califadr3.objects`_ table.  So, to compute the wavelengths in question you
could say::

  select
	  target_name,
	  califaid,
	  6563*(1+redshift) as lha,
	  6540*(1+redshift) as lri,
	  6700*(1+redshift) as lle
  from califadr3.objects                                         (1)

The wavelength/fluxes pairs, on the other hand, and in tables
`califadr3.fluxv500`_ (or v1200) and `califadr3.fluxposv500`_; in the former,
flux is given over lambda, califa id (a small integer; let's use 909,
corresponding to UGC 12519, as an example further down) and pixel coordinates,
in the latter, it is celestial positions.

Since we cannot usefully compare floats for equality in generally, and
in particular not here, to retrieve fluxes for a single wavelength you
need to restrict lambda to an interval wide enough.  How wide the
interval needs to be, you can figure out be determining the spacing of
samples. For the V500 data set, this could look like this::

	select distinct top 2 lambda
    	from califadr3.fluxv500
  	order by lambda (2)

(This is fast although there's dozens of millions of lambdas in this table
because there's an index on lambda, and there are not many distinct values).
If you try it, you'll see that we have steps of two Ångström, so you'll
want the interval to be something like 2.2 Ångström.

To obtain fluxes while while having the redshift available -- as needed
here -- you need to join the objects with the flux tables; here, you can use
a "NATURAL" join, which means all columns identically names are to be
used for joining::

  select top 5000 target_name, xindex, yindex, lambda, flux
  from
    califadr3.fluxv500
    natural join califadr3.objects
  where
    lambda between 6563*(1+redshift)-1.1 and 6563*(1+redshift)+1.1
    and target_name='UGC12519'                                 (3)

The "top 5000" is necessary here to retrieve all fluxes: to protect your
nerves, the server inserts a "top 2000" unless you order something
else.  The restriction on the califaid makes sure we don't retrieve
all fluxes between our two wavelengths in the CALIFA tables.

This table has pixel coordinates.  The physical positions for each pixel are
in a table called `califadr3.spectra`_.  To get them into your table,
you'll need another join::

  select top 5000 target_name, raj2000, dej2000, lambda, flux
  from
    califadr3.fluxv500
    natural join califadr3.objects
    natural join califadr3.spectra
  where
    lambda between 6563*(1+redshift)-1.1 and 6563*(1+redshift)+1.1
    and target_name='UGC12519'                                 (3)

At this point you could retrieve the flux maps at the three tables and
use, for instance,  TOPCAT's crossmatch functionality to do the match
client-side.  However, let's assume we want to keep processing
server-side, e.g., because we want to run this on a lot of objects and
don't want to download all the layers.

To retrieve fluxes at multiple wavelengths, you can query like this::

  select top 5000 target_name, raj2000, dej2000, lambda, flux
  from
    califadr3.fluxv500
    natural join califadr3.objects
    natural join califadr3.spectra
  where (
    lambda between 6563*(1+redshift)-1.1 and 6563*(1+redshift)+1.1
    or lambda between 6540*(1+redshift)-1.1 and 6540*(1+redshift)+1.1
    or lambda between 6700*(1+redshift)-1.1 and 6700*(1+redshift)+1.1)
    and target_name='UGC12519'                                 (5)

The problem with this is that you cannot compute the criterion for a
strong Halpha emission from this as the three fluxes are in three
different rows.  When you are in that situation, SQL offers grouping --
this means that rows sharing a criterion end up in a bag; you can then
use "aggregate functions" on these.  Note, that as most things in the
relational world, the items in the bag are not sorted.

As a general word of advice: In the SQL world, thinking in array terms
is typically going to lead to complicated and slow queries.  Try
thinking in terms of sets and matters will become manageable.

ADQL's aggregate functions are a bit limited, but for this case they're
just enough -- just compare the maximum flux to the average flux::

  select top 5000 califaid, xindex, yindex
  from
    califadr3.fluxv500
    natural join califadr3.objects
  where (
    lambda between 6563*(1+redshift)-1.1 and 6563*(1+redshift)+1.1
    or lambda between 6540*(1+redshift)-1.1 and 6540*(1+redshift)+1.1
    or lambda between 6700*(1+redshift)-1.1 and 6700*(1+redshift)+1.1)
    and target_name='UGC12519'
  group by califaid, xindex, yindex
  having (max(flux)/avg(flux)>3)                                (6)

We group on pixel coordinates here as that's much more robust (and also
somewhat faster) than going by the floating point ra/dec pairs.  To turn the
pixel coordinates to postitions you can do something with, again do a join with
califadr3.spectra as in (4), except this time you turn the entire query so
far into a subquery::

  select raj2000, dej2000 from (
  	select califaid, xindex, yindex
  	from
    	califadr3.fluxv500
    	natural join califadr3.objects
  	where (
    	lambda between 6563*(1+redshift)-1.1 and 6563*(1+redshift)+1.1
    	or lambda between 6540*(1+redshift)-1.1 and 6540*(1+redshift)+1.1
    	or lambda between 6700*(1+redshift)-1.1 and 6700*(1+redshift)+1.1)
    	and target_name='UGC12519'
  	group by califaid, xindex, yindex
  	having (max(flux)/avg(flux)>3)) as spots
    natural join califadr3.spectra

Finally, if you want to have all such "interesting" points in CALIFA,
drop the constaint on the object name::

  select raj2000, dej2000 from (
  	select califaid, xindex, yindex
  	from
    	califadr3.fluxv500
    	natural join califadr3.objects
  	where (
    	lambda between 6563*(1+redshift)-1.1 and 6563*(1+redshift)+1.1
    	or lambda between 6540*(1+redshift)-1.1 and 6540*(1+redshift)+1.1
    	or lambda between 6700*(1+redshift)-1.1 and 6700*(1+redshift)+1.1)
  	group by califaid, xindex, yindex
  	having (max(flux)/avg(flux)>3)) as spots
    natural join califadr3.spectra                           (8)

This is a fairly long-running query, which will time out on you on when
you enter it through the "synchronous" TAP endpoint that TOPCAT uses by
default (because it's simple and has little overhead).  To use the
"async" endpoint, uncheck "Synchronous" in TOPCAT's TAP dialog (or do
the equivalent thing in another client).  With this, you can turn off
your computer, take it somewhere else, and resume operations when you
get back; in this case, the job should be done in 20 minutes or so
depending on server load and similar factors.

Once you have the data, try a few of the nice VO features you get.  If
you used TOPCAT to query, the result is in a table.  With this, start
Aladin_, in TOPCAT, select Interop/Send Table To/Aladin and watch your
matches in Aladin.  Hit, e.g., "Optical" in Aladin, and you can zoom in
on the "interesting" spots and see them overplotted on sky images.

You could now load the corresponding spectrum into a spectral analysis
tool like, say Splat. To do that, in Aladin click on one point, go to
the load dialog and there the "all VO" tab.  Optionally, go to "Detailed
List", hit "Uncheck all" and just check the "CALIFA Spectra" service
(this is going to speed up your queries significantly).

Then start Splat_, in the Aladin load dialog set the search radius to
0.01' (i.e., .6 arcsec) and submit.  You should see both the V1200 and
the V500 spectra -- right click on the one you want to see, select "Open
with", "Splat", and work with your spectrum there.

If you don't want to get Splat, TOPCAT will do as well, although it has
much less of built-in knowledge about spectra -- in that case, open
TOPCAT's VO/SSA dialog, in the list of SSA services select "califa ssa",
make sure "Accept Sky Positions" is checked, in "Diameter" again say
something like 0.6 arcsec.  If you hover over a point in Aladin, you'll
get your positions filled in, and if you hit "Ok", the spectrum will be
retrieved.  If you, again, use the pushpin to make TOPCAT keep the
window open, you have a quick way of downloading spectra that look
interesting to you.

.. _topcat: http://www.star.bris.ac.uk/~mbt/topcat/
.. _adql: http://www.g-vo.org/adql
.. _aladin: http://aladin.u-strasbg.fr/aladin.gml
.. _splat: http://star-www.dur.ac.uk/~pdraper/splat/splat-vo/
.. _califadr3.spectra: /__system__/dc_tables/show/tableinfo/califadr3.spectra
.. _califadr3.objects: /__system__/dc_tables/show/tableinfo/califadr3.objects
.. _califadr3.fluxposv500: /__system__/dc_tables/show/tableinfo/califadr3.fluxposv500
.. _califadr3.fluxv500: /__system__/dc_tables/show/tableinfo/califadr3.fluxv500
	]]></meta>

	<STREAM id="fluxitems">
		<doc>common stuff for the flux tables; set the setup attribute
		to v500 or v1200.</doc>
		<nrows>2147483647</nrows>
		<onDisk>True</onDisk>
		<adql>True</adql>
		<index columns="xindex"/>
		<index columns="yindex"/>
		<index columns="califaid"/>
		<index columns="lambda"/>
		<foreignKey inTable="spectra" source="califaid,xindex,yindex"
			metaOnly="True"/>

		<meta name="description">Flux and errors versus position for CALIFA setup
			\setup. Positions are pixel indices into the CALIFA cubes.  The
			associate positions are in califadr.spectra; use
			"JOIN califadr3.spectra USING (califaid, xindex, yindex)" to join
			that table (or use the fluxpos tables).</meta>

		<column name="lambda"
			unit="0.1nm"
			tablehead="λ"
			description="Wavelength"/>
		<column name="flux"
			unit="1e-18J/(s.m**2.nm)"
			tablehead="flux"
			description="Flux"/>
		<column name="error"
			unit="1e-22J/(s.cm**2.nm)"
			tablehead="Err. flux"
			description="Error in Flux"/>

		<column original="spectra.califaid"/>
		<column original="spectra.xindex"/>
		<column original="spectra.yindex"/>
	</STREAM>

	<STREAM id="maxmeanrms">
		<doc>QC parameters come as mean, max, and rms</doc>
		<column name="\basename\+_mean"
			unit="\unit" ucd="\baseucd;stat.mean"
			tablehead="&lt;\basehead&gt;"
			description="Mean \basedesc over all pointings."
			verbLevel="\verblevel"/>
		<column name="\basename\+_max"
			unit="\unit" ucd="\baseucd;stat.max"
			tablehead="max(\basehead)"
			description="Maximal \basedesc band among all pointings."
			verbLevel="\verblevel"/>
		<column name="\basename\+_rms"
			unit="\unit" ucd="stat.error;\baseucd"
			tablehead="&amp;sigma;\basehead"
			description="RMS \basedesc band over all pointings"
			verbLevel="\verblevel"/>
		<column name="flag_\basename" type="smallint"
			ucd="meta.code.qual;\baseucd"
			tablehead="\basehead!?"
			description="Quality flag for \basedesc (determined as the most
				severe over mean, max, and rms" note="f">
			<values nullLiteral="-1"/>
		</column>
	</STREAM>

	<table id="spectra" onDisk="True" adql="True"
			mixin="//scs#q3cindex" primary="califaid,xindex,yindex">

		<mixin>//scs#pgs-pos-index</mixin>

		<index columns="accref"/>
		<index columns="ssa_location" method="GIST"/>
		<index columns="ssa_dateObs"/>
		<index columns="ssa_targname"/>

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

		<meta name="description">
			Metadata for individual spectra.  Note that the spectra result
			from reducing a complex dithering scheme and are not independent
			from one another.
		</meta>

		<mixin
			instrument="Calar Alto 3.5m PMAS/PPAK"
			fluxCalibration="ABSOLUTE"
			spectralCalibration="ABSOLUTE"
			creator="CALIFA survey"
			spectralUCD="em.wl"
			spectralUnit="0.1nm"
			fluxUCD="phot.flux.density;em.wl"
			fluxUnit="1e-22J/(s.cm**2.nm)"
			dataSource="pointed"
			creationType="spectralExtraction"
			reference="Sanchez et al, 2016, in prep"
			statSpectError="0.2e-10"
			statSpaceError="0.00025"
		>//ssap#hcd</mixin>
		<column name="raj2000" type="double precision"
			ucd="pos.eq.ra;meta.main" unit="deg"
			tablehead="RA"
			description="Right ascension of spectrum, ICRS"
			verbLevel="5"/>
		<column name="dej2000" type="double precision"
			ucd="pos.eq.dec;meta.main" unit="deg"
			tablehead="Dec"
			description="Declination of spectrum, ICRS"
			verbLevel="5"/>
		<column name="califaid" type="smallint" required="True"
			ucd="meta.id"
			tablehead="Id"
			description="CALIFA id number of the target."
			verbLevel="30"/>
		<column name="xindex" type="smallint" required="True"
			ucd="pos.cartesian.x"
			tablehead="x"
			description="X index in the CALIFA grid; V1200 indices are pixel+1000."
			verbLevel="30"/>
		<column name="yindex" type="smallint" required="True"
			ucd="pos.cartesian.y"
			tablehead="y"
			description="Y index in the CALIFA grid; V1200 indexes are pixel+1000"/>
		<column original="//ssap#instance.ssa_snr"
			description="Signal-to-noise ratio at 500 nm (V500 spectra)
				or 400 nm (V1200 spectra)"/>
	</table>

	<table id="fluxv500">
		<FEED source="fluxitems" setup="v500"/>
	</table>

	<table id="fluxv1200">
		<FEED source="fluxitems" setup="v1200"/>
	</table>



	<NXSTREAM id="fluxpositems">
		<doc>Common items for the joins between the flux tables and spectra.
		See fluxitems, and again give a setup attribute.</doc>
		<onDisk>True</onDisk>
		<adql>true</adql>

		<meta name="description">
			Data cubes of positions and fluxes in the optical for a sample  of
			galaxies, obtained by the CALIFA project in the \setup setup.

			Note that due to the dithering
			scheme, the points here do not actually correspond to raw measurements
			but instead represent a reduction of several measurements.
		</meta>

		<LOOP listItems="lambda flux error">
			<events>
				<column original="flux\setup.\\item"/>
			</events>
		</LOOP>

		<LOOP listItems="raj2000 dej2000">
			<events>
				<column original="spectra.\\item"/>
			</events>
		</LOOP>

		<viewStatement>
			CREATE VIEW \\curtable (\\colNames) AS (SELECT
				\\colNames FROM \\schema.fluxv500 JOIN \\schema.spectra
					USING (califaid, xindex, yindex))
		</viewStatement>
	</NXSTREAM>

	<table id="fluxposv500">
		<FEED source="fluxpositems" setup="v500"/>
	</table>

	<table id="fluxposv1200">
		<FEED source="fluxpositems" setup="v1200"/>
	</table>

	<table id="cubes" onDisk="True" namePath="//obscore#ObsCore">
		<meta name="description">
			Metadata for the CALIFA data cubes as delivered by the project.
		</meta>
		<meta name="_associatedDatalinkService">
			<meta name="serviceId">dl</meta>
			<meta name="idColumn">obs_publisher_did</meta>
		</meta>
		<mixin>//products#table</mixin>
		<mixin
			access_estsize="10"
			access_format="'application/x-votable+xml;content=datalink'"
			calib_level="3"
			dataproduct_type="'cube'"
			em_max="em_max"
			em_min="em_min"
			em_res_power="em_res_power"
			em_ucd="'em.wl'"
			em_xel="em_xel"
			facility_name="'Calar Alto'"
			instrument_name="'PMAS/PPAK at 3.5m Calar Alto'"
			obs_collection="'CALIFA'"
			obs_title="obs_title"
			o_ucd="'phot.flux;em.opt'"
			s_dec="s_dec"
			s_fov="0.01"
			s_pixel_scale="1"
			s_ra="s_ra"
			s_region="s_region"
			s_resolution="0.0002778"
			s_xel1="78"
			s_xel2="73"
			target_class="'Galaxy'"
			target_name="target_name"
			t_exptime="t_exptime"
			t_max="t_max"
			t_min="t_min"
		>//obscore#publish</mixin>
		<column name="califaid" type="smallint" required="1"
			ucd="meta.id;meta.main"
			tablehead="CALIFA#"
			description="CALIFA internal object key"
			verbLevel="1"
			note="i"/>
		<LOOP listItems="obs_id obs_title obs_publisher_did
			target_name t_exptime t_min t_max s_region
			t_exptime em_min em_max em_res_power">
			<events>
				<column original="\item"/>
			</events>
		</LOOP>

		<column original="accref">
			<property name="targetType"
				>application/x-votable+xml;content=Datalink</property>
			<property name="targetTitle">Datalink</property>
		</column>

		<column original="s_ra" description="Right ascension of galaxy center,
			J2000, from NED"/>
		<column original="s_dec" description="Declination of galaxy center,
			J2000, from NED"/>

		<column original="em_xel"/>

		<FEED source="maxmeanrms"
			basename="obs_ext"
			baseucd="obs.atmos.extinction;em.opt.V"
			unit="mag"
			basehead="ext_v"
			basedesc="atmospheric extinction in V band"
			verblevel="15"/>
		<FEED source="maxmeanrms"
			basename="obs_am"
			baseucd="obs.airMass"
			unit="mag"
			basehead="Airmass"
			basedesc="airmass (secz)"
			verblevel="15"/>
		<FEED source="maxmeanrms"
			basename="red_disp"
			baseucd="instr.dispersion"
			unit="0.1nm"
			basehead="disp"
			basedesc="spectral dispersion FWHM"
			verblevel="25"/>
		<FEED source="maxmeanrms"
			basename="red_cdisp"
			baseucd="instr.dispersion"
			unit="pixel"
			basehead="cdisp"
			basedesc="cross-dispersion FWHM"
			verblevel="25"/>

		<column name="red_resskyline_min"
			unit="count" ucd="stat.fit.residual;spect.line;stat.min"
			tablehead="min(skyline)"
			description="Flux residual of a bright skyline
				(at 557.7 nm for V500, 435.8 nm for V1200), minimum over all pointings"
			verbLevel="25"/>
		<column name="red_resskyline_max"
			unit="count" ucd="stat.fit.residual;spect.line;stat.max"
			tablehead="max(skyline)"
			description="Flux residual of a bright skyline
				(at 557.7 nm for V500, 435.8 nm for V1200), maximum over all pointings"
			verbLevel="25"/>
		<column name="red_rmsresskyline_max"
			unit="count" ucd="stat.fit.residual;spect.line;stat.max"
			tablehead="max(skyline)"
			description="RMS flux residual over all fibers of a bright skyline
				(at 557.7 nm for V500, 435.8 nm for V1200), maximum over all pointings"
			verbLevel="25"/>
		<column name="flag_red_skylines" type="smallint"
			ucd="meta.code.qual"
			tablehead="!Sky?"
			description="Flag denoting problems with the sky substraction"
			verbLevel="15" note="f">
			<values nullLiteral="-1"/>
		</column>

		<column name="red_meanstraylight_max"
			unit="count" ucd="instr.background;stat.mean"
			tablehead="&lt;stray&gt;"
			description="Maximum of mean straylight intensity over all pointings"
			verbLevel="25"/>
		<column name="red_maxstraylight_max"
			unit="count" ucd="instr.background;stat.max"
			tablehead="max(stray)"
			description="Maximum of maximum straylight intensity over all pointings"
			verbLevel="25"/>
		<column name="red_rmsstraylight_max"
			unit="count" ucd="instr.background"
			tablehead="&amp;sigma;stray"
			description="Maximum of RMS straylight intensity over all pointings"
			verbLevel="25"/>
		<column name="flag_red_straylight" type="smallint"
			ucd="meta.code.qual"
			tablehead="Stray!?"
			description="Flag for critical straylight."
			verbLevel="15" note="f">
			<values nullLiteral="-1"/>
		</column>

		<column name="obs_skymag_mean"
			unit="mag/arcsec**2" ucd="phot.mag.sb;instr.skyLevel;em.opt.V;stat.mean"
			tablehead="&lt;skymag&gt;"
			description="Mean V band sky surface brightness over all pointings."
			verbLevel="25"/>
		<column name="obs_skymag_rms"
			unit="mag/arcsec**2" ucd="stat.error;phot.mag.sb;instr.skyLevel;em.opt.V"
			tablehead="&amp;sigma;skymag"
			description="RMS of V band sky surface brightness over all pointings."
			verbLevel="25"/>
		<column name="flag_obs_skymag" type="smallint"
			ucd="meta.code.qual"
			tablehead="SB!?"
			description="Flag for critical sky surface brightness."
			verbLevel="15" note="f">
			<values nullLiteral="-1"/>
		</column>

		<column name="red_limsb"
			unit="mag/arcsec**2" ucd="phot.mag.sb;em.opt.B;stat.min"
			tablehead="Lim. SB"
			description="3-sigma limiting surface brightness in B band in mag."
			verbLevel="25"/>
		<column name="red_limsbflux"
			unit="1e3J.cm**-2.s**-1.m**-1.arcsec**-2"
			tablehead="Lim. SB"
			description="3-sigma limiting surface brightness in B band as flux."
			verbLevel="25"/>
		<column name="flag_red_limsb" type="smallint"
			ucd="meta.code.qual"
			tablehead="Limit!?"
			description="Flag for critical limiting surface brightness sensitivity."
			verbLevel="15" note="f">
			<values nullLiteral="-1"/>
		</column>

		<column name="red_frac_bigerr"
			unit="" ucd="instr.det.noise;arith.ratio"
			tablehead="Bad Pix."
			description="Fraction of bad pixels (error larger than five times the
				value) in this cube."
			verbLevel="25"/>
		<column name="flag_red_errspec" type="smallint"
			ucd="meta.code.qual"
			tablehead="Badpix!?"
			description="Flag indicating excessive bad pixels."
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>

		<column name="cal_qflux_g"
			ucd="phot.flux;em.opt.V;arith.ratio"
			tablehead="C/SDSS G"
			description="Average flux ratio relative to SDSS g mean over
				all pointings"
			verbLevel="25"/>
		<column name="cal_qflux_r"
			ucd="phot.flux;em.opt.R;arith.ratio"
			tablehead="C/SDSS R"
			description="Average flux ratio relative to SDSS r mean over
				all pointings"
			verbLevel="25"/>
		<column name="cal_qflux_rms"
			ucd="stat.error;em.opt;arith.ratio"
			tablehead="RMS C/SDSS"
			description="Average flux ratio relative to SDSS g and r RMS over
				all pointings"
			verbLevel="25"/>
		<column name="flag_cal_specphoto" type="smallint"
			ucd="meta.code.qual"
			tablehead="Limit!?"
			description="Flag for problems with spectro-photometric calibration."
			verbLevel="15" note="f">
			<values nullLiteral="-1"/>
		</column>

		<column name="cal_rmsvelmean"
			unit="km/s" ucd="stat.error;instr.calib"
			tablehead="WL Calib delta"
			description="RMS of radial velocity residuals of estimates based on
				3 or 4 spectral subranges wrt the estimates from the full spectrum."
			verbLevel="25"/>
		<column name="flag_cal_wl" type="smallint"
			ucd="meta.code.qual"
			tablehead="Cal λ!?"
			description="Flag for critical wavelength calibration stability."
			verbLevel="15" note="f">
			<values nullLiteral="-1"/>
		</column>

		<column name="flag_cal_imgqual" type="smallint"
			ucd="meta.code.qual"
			tablehead="Img!?"
			description="Flag for visually evident problems in this cube"
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>
		<column name="flag_cal_specqual" type="smallint"
			ucd="meta.code.qual"
			tablehead="Spec!?"
			description="Flag for visually evident problems in a 30''-aperture
				spectrum generated from this cube"
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>
		<column name="cal_flatsdss" type="smallint"
			ucd="meta.code.qual"
			tablehead="Flat!?"
			description="Flag for visually evident problems in the `SDSS flat'
				(see source paper); NULL here means: no SDSS flat applied."
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>
		<column name="flag_cal_registration" type="smallint"
			ucd="meta.code.qual"
			tablehead="Pos!?"
			description="Flag for visually evident problems in the registration
				of the synthetic broad-band image with SDSS and related procedures."
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>
		<column name="flag_cal_v1200v500" type="smallint"
			ucd="meta.code.qual"
			tablehead="Match!?"
			description="Set if a visual check for the 30''-aperture integrated
				spectra in V500, V1200, and COMB showed problems."
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>

		<column name="obs_seeing_mean"
			unit="arcsec" ucd="instr.obsty.seeing;stat.mean"
			tablehead="&lt;seeing&gt;"
			description="Mean FWHM seeing over all pointings"
			verbLevel="15"/>
		<column name="obs_seeing_rms"
			unit="arcsec" ucd="stat.error;instr.obsty.seeing"
			tablehead="σ(seeing)"
			description="RMS of FWHM seeing over all pointings"
			verbLevel="15"/>

		<column name="cal_snr1hlr"
			ucd="stat.snr"
			tablehead="SNR"
			description="Signal to noise ratio at the half-light ratio."
			verbLevel="15"/>
		<column name="notes" type="text"
			ucd="meta.note"
			tablehead="Notes"
			description="Notes"
			verbLevel="15"/>
		<column name="setup" type="text"
			ucd="meta.code;instr"
			tablehead="Setup"
			description="Instrument setup (V500, V1200, or COMBO)"
			verbLevel="15"
			note="s"/>

		<meta name="note" tag="f">
			Each flag may be NULL (undefined), 0 (good quality), 1 (warning:
			minor issues that do not significantly affect the quality), or
			2 (bad: significant issues affecting the quality)
		</meta>

		<meta name="note" tag="i">
			The CALIFA id is
			
			* &lt;1000 for objects from the diameter-selected mother
			  sample (cf. 2014A&amp;A...569A...1W)
			* 1000 .. 1999 for the dwarf galaxy extension (34 observed)
			* 2000 .. 2999 for companions of main sample galaxies (29 observed)
			* 3000 .. 3999 for the early-type galaxy extension (36 observed)
			* 4000 .. 4999 for galaxies observed during pilot studies (3 included)
			* 5000 .. 5999 for the supernova environments extension (14 observed)
			* 7001 and 8000 for two solitary galaxies
			* 9000 .. 9999 for the compact early-type galaxy extension (17 included)
		</meta>

		<meta name="note" tag="s">
			The califa cubes come in three setups:

			* V1200 – R about 1650, covering 340 .. 484 nm, with significant
			  vignetting outside of 365 .. 462 nm;  exposure time 1800 s per pointing
			  (3 pointings per object).
			* V500 – R about 850, covering 374.5 .. 750 nm, with significant
			  vignetting outside of 424 .. 714 nm; exposure time 900 s per pointing
			  (3 pointings per object).
			* COMBO – V500 data combined with V1200 data in the overlap region.
			  These have better S/N there.  The unvignetted wavelength range
			  for these is 370-714 nm.
		</meta>
	</table>

	<coverage>
		<updater sourceTable="cubes"/>
		<spectral>2.64824e-19 5.44232e-19</spectral>
		<temporal>55357.9 57378.2</temporal>
		<spatial>6/43,50,56,131,545,552,554,556,568,573,620-621,626-627,721,733,738,907-909,948-949,975,995,1701,2206,2228-2229,2248,2251,2254,2272-2274,2276,2290,2641,4053,4167,4178,4279,4342,4445,4454,4472,4477,4507,4511,4526,4647,4654-4655,4667,4688,4696,4704,4712,4719,4731,4753-4754,4772,4799,4801,4806-4807,4813,4829,4840,4843-4844,4850,4912,4950,4957,4993,5023-5024,5028,5030,5075,5115,5148,5241,5247,5298,5332,5399,5482,5505,5530,5533,5579,5586,5591,5593,5596,5648,5695,5701,5704,5757,5862,5889,5942,5951,5975,5980,6000,6011,6045-6046,6074,6087,6093,6096,6116,6162,6212,6214,6216,6240-6241,6261,6270,6342,6362,6366,6411,6448,6465,6501,6532,6541,6547,6554,6607,6630,6977,7198,7204,7214,7308-7309,7312,7340,7386,7442,7501,7559-7560,7572,7584,7589,7591,7696,7783,7822,7824,7834,8045,8164,8195,8297,8327,8331,8341,8366,8376,8458,8477,8479-8480,8492,8498,8522,8536,8552,8557,8562-8563,8570,8589,8605,8621,8646,8656,8673,8705,8719,8728,8743,8752,8754,8825,8883,8898,8900,8919,8921-8922,8973,8984,8992,9098,9101,9124,9126,9133,9141,9187,9190,9216,9220,9381,9468,9470-9471,9622,9628,9630,9695,9718,9798,9812,9859,9873-9875,9882-9883,9885,9891,9946,9974,9991,10025,10111,10113,10144,10182-10183,10189,10200,10203,10213,10220-10221,10280,10286,10379-10381,10398,10400-10401,10414,10422,10434,10448,10485,10501,10511-10512,10515,10518-10520,10523-10524,10530,10540,10560,10566,10570,10577,10583,10586,10588,10594-10595,10609,10612,10621,10627-10629,10637,10661-10662,10669,10674,10705,10711,10719,10721,10727,10736,10739-10740,10752,10773,10778,10846,10921,10987,11010,11026,11044,11070,11111-11112,11164,11173,11196,11272,11290,11314,11325-11326,11341-11342,11369,11372,11374,11377,11383,11387,11392,11396,11418,11433,11435,11443,11451,11525,11544,11761,11777,11789,11793,11800-11801,11804,11832,11905,11944,12306,12378,12399,12532,12709,13316,13380-13381,13426,13529,15002,15004,16007,16696,16702,16787-16788,16793,17233-17234,17237-17238,17350,17392,17398,17486,17492,17499,17502,17509,17511,17516,17520,17571,17578,17661-17663,17667,17672,17745,17747-17748,17751,17758,17761,17767,17773,17819,17912,17998,18008,18069,18071,18081,18173,18241-18242,18252,18295,18300-18302,18381,18383,18523,18775,18779,18781,18790,18800,18838,18854,18931-18932,18934-18935,18941,19027,19073,19108,19186,19227,19260,19267,19325,19387,19417,19432,19458,19487,19489,19508,19515,19536-19538,19593,19618,19633,19642,19678,19696,19749,19848,19939-19940,19954,19981,19983-19984,19991,19996-19997,20009,20020,20023,20031-20033,20040,20193,20254,20279,20417-20418,20429,21857,22353,22356,22361,22364,22651-22652,22654-22655,22737,22740-22742,22826,23051,23072,23076,23182,23204,23206,23208,23273,23337,23353,23356,23401,23405,24055,26102,26108,26125,26318,26451,26495,26551,27133,27259,27368,27559,27567,27608,27719,27748,27830,27862,27912,27914-27915,28112,28213,28263-28264,28294,28310,28322,28365,28430,28458,28491,28497,28513,28522,28579,28606,28617,28639,29973,31478,31546,31628,31642-31643,31688-31689,31736-31737,32462,32471,32478,32482,32555,32559,32682,36604,36733,36765,36793,36797,36805,36847,36857,36861,48880,49084,49121,49130,49150</spatial>
	</coverage>

	<STREAM id="sdssphoto">
		<column name="mag\band"
			ucd="phot.mag;em.opt.\ucdband"
			tablehead="m_\band"
			description="Magnitude in the \band band. This is from a growth
				curve analysis of the SDSS imagery for galaxies
				in the mother sample (id&lt;1000).  Otherwiese, it is the SDSS DR7/12
				petrosian magnitude."
			verbLevel="15"/>
		<column name="err_mag\band"
			ucd="stat.error;phot.mag;em.opt.\ucdband"
			tablehead="Err. m_\band"
			description="Error in m_\band"
			verbLevel="25"/>
		<column name="\band\+_ext"
			ucd="phys.absorption;em.opt.\ucdband"
			tablehead="Ext. \band"
			description="Extinction in the \band band"
			verbLevel="15"/>
		<column name="abs_\band\+_min"
			ucd="phot.mag;em.opt.\ucdband;stat.min"
			tablehead="M_\band min"
			description="3 sigma lower limit of the absolute magnitude in \band"
			verbLevel="15"/>
		<column name="abs_\band\+_max"
			ucd="phot.mag;em.opt.\ucdband;stat.max"
			tablehead="M_\band max"
			description="3 sigma upper limit of the absolute magnitude in \band"
			verbLevel="15"/>
	</STREAM>

	<STREAM id="sdssphotomap">
		<events>
			<map key="mag\band" source="\band"/>
			<map key="err_mag\band" source="\band\+_err"/>
		</events>
	</STREAM>

	<table id="objects" onDisk="True" adql="true">
		<meta name="description">
			Object data for DR3 sample.

			The photometric and derived quantities are from growth curve
			analysis of the SDSS images for galaxies from the mother sample
			(califaid&lt;1000), from SDSS DR7/12 photometry otherwise.
		</meta>

		<stc>
			Position J2000 "raj2000" "dej2000"
			Redshift OPTICAL "redshift"
		</stc>

		<foreignKey inTable="cubes" source="califaid" metaOnly="True"/>

		<column original="cubes.target_name"/>
		<column original="cubes.califaid"/>
		<column name="raj2000" type="double precision"
			ucd="pos.eq.ra;meta.main" unit="deg"
			tablehead="RA"
			description="Right ascension of the galaxy center, J2000 (from NED)"
			verbLevel="1"/>
		<column name="dej2000" type="double precision"
			ucd="pos.eq.dec;meta.main" unit="deg"
			tablehead="Dec"
			description="Right ascension of the galaxy center, J2000 (from NED)"
			verbLevel="1"/>
		<column name="redshift"
			ucd="src.redshift"
			tablehead="z"
			description="Redshift, from growth curve analysis photometry for
				mother sample galaxies, from SDSS DR7/12 petrosian photometry
				otherwise."
			verbLevel="1"/>
		<column name="sdss_z"
			ucd="src.redshift"
			tablehead="z_sdss"
			description="Redshift taken from SDSS DR7"
			verbLevel="15"/>
		<column name="maj_axis"
			unit="arcsec" ucd="phys.angSize;src"
			tablehead="Maj."
			description="Apparent isophotal major axis from SDSS"
			verbLevel="1"/>

		<column name="mstar"
			unit="log(solMass)" ucd="phys.mass"
			tablehead="log(M)"
			description="Stellar mass "
			verbLevel="5"/>
		<column name="mstar_min"
			unit="log(solMass)" ucd="phys.mass;stat.min"
			tablehead="Min log(M)"
			description="3 sigma lower limit of Stellar mass "
			verbLevel="5"/>
		<column name="mstar_max"
			unit="log(solMass)" ucd="phys.mass;stat.max"
			tablehead="Max log(M)"
			description="3 sigma upper limit of Stellar mass "
			verbLevel="5"/>
		<column name="chi2"
			ucd="stat.fit.chi2"
			tablehead="χ²"
			description="χ² of best fit"
			verbLevel="15"/>
		<column name="vmax_nocorr"
			unit="Mpc**3" ucd="stat.weight"
			tablehead="V_max"
			description="Survey volume for this galaxy from apparent
				isophotal diameter and measured redshift (cf. 2014A&amp;A...569A...1W)"
			verbLevel="25"/>
		<column name="vmax_denscorr"
			unit="Mpc**3" ucd="stat.weight"
			tablehead="V_max'"
			description="V_max additionally corrected for cosmic variance
				(cf. 2014A&amp;A...569A...1W)"
			verbLevel="25"/>

		<FEED source="sdssphoto" band="u" ucdband="U"/>
		<FEED source="sdssphoto" band="g" ucdband="V"/>
		<FEED source="sdssphoto" band="r" ucdband="R"/>
		<FEED source="sdssphoto" band="i" ucdband="I"/>
		<FEED source="sdssphoto" band="z" ucdband="I"/>

		<column name="hubtyp" type="text" required="true"
			ucd="src.morph.type"
			tablehead="Type"
			description="Morphological type from CALIFA's own visual classification
				(see 2014A&amp;A...569A...1W for details). (M) indicates definite
				merging going on, (m) indicates likely merging, (i) indicates
				signs of interaction."
			verbLevel="15"/>
		<column name="minhubtyp" type="text" required="true"
			ucd="src.morph.type"
			tablehead="Min. Type"
			description="Earliest morphological type in CALIFA's estimation"
			verbLevel="25"/>
		<column name="maxhubtyp" type="text" required="true"
			ucd="src.morph.type"
			tablehead="Max. Type"
			description="Latest morphological type in CALIFA's estimation"
			verbLevel="25"/>
		<column name="bar" type="text"
			ucd="meta.code;src.morph"
			tablehead="Bar"
			description="Bar strength, A -- strong bar, AB -- intermediate,
				B -- weak bar; with minimum and maximum estimates."
			verbLevel="15"/>
		
		<LOOP listItems="comb v1200 v500">
			<events>
				<column name="flag_release_\item" type="smallint" required="True"
					ucd="meta.code;meta.dataset"
					tablehead="\upper{\item}"
					description="Cube in setup \upper{\item} available?"
					verbLevel="15"/>
			</events>
		</LOOP>

		<column name="axis_ratio"
			ucd="phys.angSize;arith.ratio"
			tablehead="Axis ratio"
			description="b/a axis ratio of a moment-based anaylsis of
				the SDSS DR7 images (cf. 2014A&amp;A...569A...1W)."
			verbLevel="15"/>
		<column name="position_angle"
			unit="deg" ucd="pos.posAng"
			tablehead="PA"
			description="Position angle wrt north."
			verbLevel="15"/>
		<column name="el_hlr"
			ucd="phys.angSize;src" unit="arcsec"
			tablehead="HLR"
			description="Petrosian half-light radius in r band"
			verbLevel="15"/>

		<column name="modmag_r"
			unit="mag" ucd="phot.mag;meta.modelled"
			tablehead="m_r Model"
			description="Model magnitude in r band"
			verbLevel="15"/>
	</table>

	<table id="instance" onDisk="False">
		<mixin ssaTable="spectra"
			spectralDescription="Wavelength"
			fluxDescription="Flux"
			>//ssap#sdm-instance</mixin>
		<meta name="description">A spectrum from the CALIFA project</meta>
		<column original="fluxv500.error"/>
	</table>


	<procDef id="addFiberMeta" type="apply">
		<doc>
		CALIFA dithers their observations from three pointings and several
		fibers (in a way I don't quite understand).  Several values are
		obtained by manipulating sequences of these individual observations.
		The headers all start with PPAK, then Pn(Fm).

		This apply computes such values.
		</doc>
		<setup>
			<code>
			import operator

			_SUB_OBS_KEYS = ( # V1200 keys
				"P1F1 P1F2 P1F3 P2F1 P2F2 P2F3 P3F1 P3F2 P3F3".split()+
				# V500 keys
				"P1 P2 P3".split())

			def getForAllPointings(prefixes, header, keyword):
				res = []
				for prefix in prefixes:
					for subKey in _SUB_OBS_KEYS:
						key = "%sPPAK %s %s"%(prefix, subKey, keyword)
						if key in header:
							res.append(header[key])
				return res
			</code>
		</setup>
		<code>
			prefixes = {
				"V500": ["V500 "],
				"V1200": ["V1200 "],
				"COMB": ["V500 ", "V1200 "]}[@setup]

			mjds = getForAllPointings(prefixes, vars, "MJD_OBS"
				)+getForAllPointings(prefixes, vars, "MJD-OBS")
			if not mjds:
				base.ui.notifyWarning("No pointings found for "+\inputRelativePath)
				vars["meanDateObs"] = vars["t_min"] = \
					vars["t_max"] = vars["cumulativeExposure"] = None

			else:
				vars["meanDateObs"] = sum(mjds)/len(mjds)
				vars["t_min"] = min(mjds)
				vars["t_max"] = max(mjds)

				vars["cumulativeExposure"] = sum(
					getForAllPointings(prefixes, vars, "EXPTIME"))
		</code>
	</procDef>

	<STREAM id="objectparse">
		<doc>The califa cubes have totally free-form object names.
		This stream normalises them using a huge translation table.
		The result is a key cleanedobject.
		</doc>
		<apply procDef="//procs#mapValue">
			<bind name="destination">"cleaned_object"</bind>
			<bind name="failuresMapThrough">True</bind>
			<bind name="sourceName">"califa/res/dr3namemap"</bind>
			<bind name="value">@OBJECT.split("_")[-1]</bind>
		</apply>
	</STREAM>

	<data id="import_spectra">
		<property name="previewDir">previews3/spectra</property>
		<sources recurse="True">
			<pattern>datadr3/*.rscube.fits</pattern>
			<!-- for spectra and in-DB cubes, we don't want V500 if we can
			have COMB, too.  Hence, we have a kill file for those.
			-->
			<ignoreSources fromfile="res/v500withcomb.txt"/>
		</sources>

		<customGrammar module="res/cubegrammar3" isDispatching="True">
			<rowfilter name="makeAccref">
				<code>
					@accref = @obsId
					@pubDID = getStandardPubDID(@accref)
					yield row
				</code>
			</rowfilter>


			<rowfilter procDef="//products#define" name="defineSpectrum">
				<bind name="fsize">50000</bind>
				<bind name="mime">"application/x-votable+xml"</bind>
				<bind name="path">makeAbsoluteURL(
					"\rdId/sdl/dlget?ID="+urllib.parse.quote(@pubDID))</bind>
				<bind name="table">"califadr3.spectra"</bind>
				<bind name="accref">@accref</bind>
				<bind name="preview">\standardPreviewPath</bind>
				<bind name="preview_mime">"image/png"</bind>
			</rowfilter>
		</customGrammar>

		<make table="spectra" role="spectra">
			<rowmaker idmaps="*">
				<apply procDef="addFiberMeta" name="addFiberMeta"/>
				<var name="specLo">@CRVAL3+(1-@CRPIX3)*@CDELT3</var>
				<var name="specHi">@CRVAL3+(@NAXIS3-@CRPIX3)*@CDELT3</var>
				<var name="specExt">@specHi-@specLo</var>
				<var name="specMid">(@specHi+@specLo)/2</var>

				<FEED source="objectparse"/>

				<var name="raj2000">@RA</var>
				<var name="dej2000">@Dec</var>
				<var name="califaid">@CALIFAID</var>

				<apply procDef="//ssap#setMeta" name="setSSAPMeta">
					<bind name="alpha">@RA</bind>
					<bind name="aperture">0.0003</bind>
					<bind name="bandpass">"OPTICAL"</bind>
					<bind name="dateObs">@meanDateObs</bind>
					<bind name="cversion">3</bind>
					<bind name="delta">@Dec</bind>
					<bind name="dstitle">"CALIFA %s %s-%s-%s"%(
						@cleaned_object, @setup, @raInd,  @decInd)</bind>
					<bind name="length">@NAXIS3</bind>
					<bind name="pubDID">getStandardPubDID(@prodtblAccref)</bind>
					<bind name="redshift">@MED_VEL/3e5</bind>
					<bind name="snr">@computedSNR if @computedSNR==@computedSNR else None</bind>
					<bind name="specext">@specExt*1e-10</bind>
					<bind name="specmid">@specMid*1e-10</bind>
					<bind name="specstart">@specLo*1e-10</bind>
					<bind name="specend">@specHi*1e-10</bind>
					<bind name="targname">@cleaned_object</bind>
					<bind name="targclass">"Galaxy"</bind>
					<bind name="timeExt">@cumulativeExposure</bind>
				</apply>
			</rowmaker>
		</make>
	</data>

	<STREAM id="fluximportitems">
		<doc>Common stuff for importing the fluxpos tables for the two setups.
		You'll need to give the setup parameter (v500 or v1200).
		</doc>
		<directGrammar id="fluxbooster" cBooster="res/booster.c"
			customFlags="-lcfitsio"/>
		<make table="flux\setup" role="fluxes"/>
		<dependents>make_fluxpos\setup</dependents>
	</STREAM>

	<data id="import_fluxv500">
		<sources recurse="True">
			<pattern>datadr3/V500/*.rscube.fits</pattern>
			<pattern>datadr3/COMB/*.rscube.fits</pattern>
			<ignoreSources fromfile="res/v500withcomb.txt"/>
		</sources>
		<FEED source="fluximportitems" setup="v500"/>
	</data>

	<data id="import_fluxv1200">
		<sources recurse="True">
			<pattern>datadr3/V1200/*.rscube.fits</pattern>
		</sources>
		<FEED source="fluximportitems" setup="v1200"/>
	</data>

	<!-- the following two are dependents of the manual imports; they just create
	the necessary joins -->
	<LOOP listItems="v500 v1200">
		<events>
			<data id="make_fluxpos\item" auto="False">
				<make table="fluxpos\item"/>
			</data>
		</events>
	</LOOP>

	<data id="import_cubes">
		<property key="previewDir">cubePreviews</property>
		<sources recurse="True" id="allsources">
			<pattern>datadr3/*.rscube.fits</pattern>
		</sources>
		<fitsProdGrammar qnd="True">
			<rowfilter procDef="//products#define">
				<bind key="table">"\schema.cubes"</bind>
				<bind key="path">\dlMetaURI{dl}</bind>
				<bind key="mime">'application/x-votable+xml;content=datalink'</bind>
				<bind key="fsize">10000</bind>
				<bind key="preview">\standardPreviewPath</bind>
				<bind key="preview_mime">"image/jpeg"</bind>
				<bind key="embargo">datetime.datetime(2016, 4, 10)</bind>
			</rowfilter>
		</fitsProdGrammar>
		<make table="cubes">
			<rowmaker idmaps="*">
				<!-- we have quality control parameters in FITSes in auxdata
				containing additional metadata that didn't make in into the header.
				Rather than import it into a separate table and join it here, I
				read it in one go and add keys from a dict made from it -->

				<ignoreOn>
					<keyIs key="FLAG_RELEASE" value="0"/>
				</ignoreOn>

				<apply name="addSetup">
					<code>
						if "V1200" in \inputRelativePath:
							@setup = "V1200"
							@em_xel = 1701
						elif "V500" in \inputRelativePath:
							@setup = "V500"
							@em_xel = 1877
						elif "COMB" in \inputRelativePath:
							@setup = "COMB"
							@em_xel = 1901
						else:
							@setup = "UNKNOWN"
					</code>
				</apply>

				<apply name="addAuxiliaryMeta">
					<setup>
						<code>
							from gavo.utils import pyfits

							def nanToNone(val):
								if val!=val:
									return None
								return val
								
							def fitsTableToDict(relPath, setup):
								rd = parent.parent.table.rd
								hdus = pyfits.open(rd.getAbsPath(relPath))
								fitsTable = hdus[1].data
								names = [n.lower() for n in fitsTable.dtype.names]
								return dict(((d["califaid"], setup), d)
									for d in (
										dict(zip(names, map(nanToNone, row)))
											for row in fitsTable))
							
							@utils.memoized
							def getQCFlags():
								res = fitsTableToDict(
									"datadr3/QCflags_std_V500_DR3.fits", "V500")
								res.update(
									fitsTableToDict("datadr3/QCflags_std_V1200_DR3.fits",
										"V1200"))
								res.update(
									fitsTableToDict("datadr3/QCflags_std_COMB_DR3.fits",
										"COMB"))
								return res

							@utils.memoized
							def getQCParams():
								res = fitsTableToDict(
									"datadr3/QCpars_std_V500_DR3.fits", "V500")
								res.update(
									fitsTableToDict(
										"datadr3/QCpars_std_V1200_DR3.fits", "V1200"))
								res.update(
									fitsTableToDict(
										"datadr3/QCpars_std_COMB_DR3.fits", "COMB"))
								return res
						</code>
					</setup>

					<code>
						vars.update(getQCFlags()[@CALIFAID, @setup])
						if @setup!="COMB":
							vars.update(getQCParams()[@CALIFAID, @setup])
					</code>
				</apply>

				<apply procDef="addFiberMeta" name="addFiberMeta"/>
				<apply name="getApproximateCoverage">
					<code>
					# assume CRVAL1, CRVAL2 are approximate center and make a circle
					# from it
					vars["roughCircle"] = pgsphere.SCircle(
						pgsphere.SPoint.fromDegrees(@CRVAL1, @CRVAL2),
						1/60.*math.pi/180)
					vars["s_ra"] = @CRVAL1
					vars["s_dec"] = @CRVAL2
					</code>
				</apply>

				<FEED source="objectparse"/>

				<var key="obs_id">\standardPubDID</var>
				<map key="obs_title">"CALIFA %s %s"%(@setup, @cleaned_object)</map>
				<map key="obs_publisher_did"
					>@obs_id</map>
				<map dest="s_region">@roughCircle.asPoly()</map>
				<map dest="t_exptime">@cumulativeExposure</map>
				<map dest="califaid">@CALIFAID</map>
				<map dest="target_name">@cleaned_object</map>
				<map dest="red_resskyline_min">vars.get("red_res4358_min",
					vars.get("red_res5577_min"))</map>
				<map dest="red_resskyline_max">vars.get("red_res4358_max",
					vars.get("red_res5577_max"))</map>
				<map dest="red_rmsresskyline_max">vars.get("red_res4358_rms",
					vars.get("red_res5577_rms"))</map>

				<map dest="em_min">(@CRVAL3+(1-@CRPIX3)*@CDELT3)*1e-10</map>
				<map dest="em_max">(@CRVAL3+(@NAXIS3-@CRPIX3)*@CDELT3)*1e-10</map>
				<map dest="em_res_power">@CRVAL3/@CDELT3</map>

				<LOOP>
					<codeItems>
						for col in context.getById("cubes"):
							if col.name.startswith("flag_"):
								yield {"item": col.name}
					</codeItems>
					<events>
						<map dest="\item">parseWithNull(@\item, int, -1)</map>
					</events>
				</LOOP>

			</rowmaker>
		</make>
	</data>

	<data id="import_objects">
		<!-- this comes split into a plethora for FITSes each containing
		a few columns.  In order to keep my sanity, I've made the embedded
		grammar just collect data from all files, accumulating by califaid
		and normalising the  column names.  Once I've seen all the files I
		expect, I start returning data -->
		<sources pattern="datadr3/CALIFA*.fits"/>
		<embeddedGrammar notify="True">
			<iterator>
				<setup>
					<code>
						from gavo.utils import pyfits
						rawdicts = {}
						filesRead = 0
					</code>
				</setup>
				<code>
					global filesRead
					table = pyfits.open(self.sourceToken)[1].data
					colNames = [c.name.lower() for c in table.columns.columns]
					if "classnum" in self.sourceToken:
						colNames[-3:] = ["num_"+n for n in colNames[-3:]]
					if "Mstar" in self.sourceToken:
						colNames[-15:] = ["abs_"+n for n in colNames[-15:]]
					rows = [dict(zip(colNames, tuple)) for tuple in table]

					idCol = "CALIFAID"
					if not idCol in rows[0]:
						idCol = "califaid"

					for row in rows:
						rawdicts.setdefault(row[idCol], {}).update(row)
					
					filesRead += 1
					if filesRead==9: # that many I expect
						for rawdict in rawdicts.values():
							yield rawdict
				</code>
			</iterator>
		</embeddedGrammar>

		<make table="objects">
			<rowmaker idmaps="*">
				<ignoreOn>
					<!-- these are objects that are in the catalog but have
					no released data; we skip them -->
					<keyMissing key="flag_release_comb"/>
				</ignoreOn>

				<simplemaps>
					target_name: dbname,
					raj2000: ra,
					dej2000: de,
					axis_ratio: ba,
					magu: u,
					magg: g,
					magr: r,
					magi: i,
					magz: z,
					err_magu: u,
					err_magg: g,
					err_magr: r,
					err_magi: i,
					err_magz: z,
					redshift: bestz,
					maj_axis: isoa_r
				</simplemaps>

				<apply name="nukeSillyVmax">
					<code>
						if not @vmax_nocorr>0:
							@vmax_nocorr = None
						if not @vmax_denscorr>0:
							@vmax_denscorr = None
					</code>
				</apply>

				<apply name="makeMergeSign">
					<code>
						if @minmerg=='M':
							@mergesig = ' (M)'
						elif @merg=='M':
							@mergesig = ' (m)'
						elif @minmerg=='M':
							@mergesig = ' (i)'
						else:
							@mergesig = ''
					</code>
				</apply>

				<map key="sdss_z">parseWithNull(@redshift, float, -9999.0)</map>
				<map key="bar">"%s (%s .. %s)"%(@bar, @minbar, @maxbar
					) if @minbar!=@maxbar else @bar</map>
				<map key="hubtyp">"%s %s%s"%(
					@hubtyp, @hubsubtyp, @mergesig)</map>
				<map key="minhubtyp">"%s %s"%(
					@minhubtyp, @minhubsubtyp)</map>
				<map key="maxhubtyp">"%s %s"%(
					@minhubtyp, @maxhubsubtyp)</map>
				<map key="position_angle">parseWithNull(@pa_align, float,
					-9999.)</map>
			</rowmaker>
		</make>
	</data>

	<data id="tables" auto="false">
		<meta name="title">CALIFA DR3 tables</meta>
		<make table="spectra"/>
		<make table="fluxposv500"/>
		<make table="fluxposv1200"/>
		<make table="cubes"/>
		<make table="objects"/>
		<publish/>
	</data>

	<data id="makePartialSpectrum">
		<embeddedGrammar>
			<iterator name="iterOneSpec">
				<setup>
					<code>
						from gavo.utils import pyfits
						from gavo.utils import fitstools
					</code>
				</setup>
				<code>
				parts = re.match(r"(.*)-(\d+)-(\d+)$",
					self.sourceToken["accref"]).groups()
				xInd, yInd = int(parts[1]), int(parts[2])
				hdus = pyfits.open(
					os.path.join(base.getConfig("inputsDir"),
					parts[0]+".rscube.fits"))
				# see section 4 of the paper
				values, errors, errWeights, valid = hdus[:4]
				fluxes = values.data[:,yInd, xInd]
				fluxErrors = errors.data[:,yInd, xInd]
				isNull = valid.data[:, yInd, xInd]
				
				lambdaAxis = fitstools.WCSAxis.fromHeader(hdus[0].header, 3,
					forceSeparable=True)

				for ind, (flux, error, skip) in enumerate(
						zip(fluxes, fluxErrors, isNull)):
					if not skip:
						yield {"flux": flux, "spectral": lambdaAxis.pix0ToPhys(ind),
									"error": error}
				</code>
			</iterator>
		</embeddedGrammar>
		<make table="instance">
			<parmaker>
				<apply procDef="//ssap#feedSSAToSDM"/>
			</parmaker>
		</make>
	</data>

	<service id="dl" allowed="dlget,dlmeta,dlasync">
		<meta name="title">CALIFA Cube Datalink Service</meta>
		<meta name="_example" title="Basic Example">
			CALIFA cubes can be cut out along RA, DEC, and spectral axes.
			CIRCLE and POLYGON cutouts yield bounding boxes.  Also note that the
			coverage of CALIFA cubes is hexagonal in space.  This explains
			the empty area when cutting out :genparam:`CIRCLE(225.5202 1.8486 0.001)`
			:genparam:`BAND(366e-9 370e-9)` on
			:dl-id:`ivo://org.gavo.dc/~?califa/datadr3/V1200/UGC9661.V1200.rscube.fits`.
			
		</meta>
		<datalinkCore>
			<metaMaker name="addRelated" semantics="#counterpart">
				<code>
					accref = getAccrefFromStandardPubDID(descriptor.pubDID)
					setups = {
						"V500": "larger coverage lower resolution",
						"COMB": "larger coverage lower resolution high-SNR",
						"V1200": "smaller coverage higher resolution"}

					for srcSetup in setups:
						if srcSetup in accref:
							break
					else:
						raise NotImplementedError("Unknown setup")
					
					for destSetup in setups:
						if srcSetup==destSetup:
							continue
						newAccref = accref.replace(srcSetup, destSetup)
						if os.path.exists(
								os.path.join(base.getConfig("inputsDir"), newAccref)):
							yield descriptor.makeLink(
								makeAbsoluteURL("califa/q3/dl/dlmeta?ID=%s"%(
									urllib.parse.quote(getStandardPubDID(newAccref)))),
								contentType=base.votableType+";content=datalink",
								description="This cube, %s"%setups[destSetup],
								contentQualifier="#cube")
				</code>
			</metaMaker>

			<metaMaker name="addAncient" semantics="#old-version">
				<setup>
					<par key="dr2v1200template"
						>"%s/datadr2/%%s.V1200.rscube.fits"%rd.resdir</par>
					<par key="dr2v500template"
						>"%s/datadr2/%%s.V500.rscube.fits"%rd.resdir</par>
					<par key="dr1v1200template"
						>"%s/data/V1200/reduced_v1.3c/%%s.V1200.rscube.fits"%rd.resdir</par>
					<par key="dr1v500template"
						>"%s/data/V500/reduced_v1.3c/%%s.V500.rscube.fits"%rd.resdir</par>
				</setup>
				<code>
					objId = os.path.basename(descriptor.pubDID).split(".")[0]
					for template, descFrag, dlid in [
							(dr2v1200template, "2 medium resolution (V1200)",
								"califa/q2#dl"),
							(dr2v500template, "2 low resolution (V500)",
								"califa/q2#dl"),
							(dr1v1200template, "1 medium resolution (V1200)",
								"califa/q#dl"),
							(dr1v500template, "1 low resolution (V500)",
								"califa/q#dl")]:
						path = os.path.join(base.getConfig("inputsDir"), template%objId)
						if os.path.exists(path):
							destLink = base.resolveCrossId(dlid).getURL("dlmeta"
									)+"?ID="+urllib.parse.quote(getStandardPubDID(path))
							yield descriptor.makeLink(destLink,
								description="This cube in Data Release "+descFrag,
								contentType="application/x-votable+xml;content=datalink",
								contentLength=10000,
								contentQualifier="#cube")
				</code>
			</metaMaker>

			<descriptorGenerator procDef="//soda#fits_genDesc"
					name="genFITSDesc">
				<bind key="accrefPrefix">'califa/data'</bind>
				<bind key="descClass">DLFITSProductDescriptor</bind>
				<bind key="contentQualifier">'#cube'</bind>
			</descriptorGenerator>

			<FEED source="//soda#fits_standardDLFuncs" spectralAxis="3"/>
		</datalinkCore>
	</service>

	<service id="sdl" allowed="dlget,dlmeta">
		<meta name="title">CALIFA Spectral Datalink Service</meta>
		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind name="ssaTD">"\rdId#spectra"</bind>
			</descriptorGenerator>

			<metaMaker semantics="#progenitor">
				<code>
					if descriptor.pubDID is None:
						return
					cubeDID = "-".join(descriptor.pubDID.split("-")[:-2])+".rscube.fits"
					yield descriptor.makeLink(
						rd.getById("dl").getURL("dlmeta")+"?ID="+urllib.parse.quote(cubeDID),
						description="Full data cube this spectrum was taken from",
						contentType="application/x-votable+xml;content=datalink",
						contentLength=10000,
						contentQualifier="#cube")
				</code>
			</metaMaker>
			<dataFunction procDef="//soda#sdm_genData">
				<bind name="builder">"\rdId#makePartialSpectrum"</bind>
			</dataFunction>
			<FEED source="//soda#sdm_plainfluxcalib"/>
			<FEED source="//soda#sdm_cutout"/>
			<FEED source="//soda#sdm_format"/>
		</datalinkCore>
	</service>

	<service id="cubesearch" allowed="form">
		<meta name="title">GAVO CALIFA Cubes</meta>
		<dbCore queriedTable="cubes">
			<FEED source="//siap2#POSpar"/>
			<condDesc>
				<inputKey original="target_name" showItems="20">
					<values fromdb="target_name from \schema.cubes order by
						target_name"/>
				</inputKey>
			</condDesc>
		</dbCore>
	</service>

	<service id="s" allowed="ssap.xml,external,form">
		<meta name="description">
			Split spectra from the CALIFA DR3 cubes.  This service serves one spectrum
			each per pixel in each cube where there is at least one valid
			spaxel.  Where both V500 and COMB data is available, COMB spectra
			are served.  WARNING: The individual spectra are not independent.
			Also, error estimates over wide spectral ranges based on the
			error estimates served here are unreliable.
		</meta>
		
		<ssapCore queriedTable="spectra">
			<property name="previews">auto</property>
			<FEED source="//ssap#hcd_condDescs"/>
		</ssapCore>
		<meta name="shortName">califa ssa</meta>
		<meta name="title">CALIFA DR3</meta>
		<meta name="ssap.dataSource">survey</meta>
		<meta name="ssap.testQuery">POS=286.4171792%2C63.92494278&amp;SIZE=0.001</meta>
		<meta name="ssap.creationType">cutout</meta>
		<publish render="ssap.xml" sets="ivo_managed"/>
		<publish render="form" sets="ivo_managed,local" service="cubesearch"/>
	</service>

	<regSuite id="spectratests">
		<regTest title="CALIFA SSA works">
			<url RESPONSEFORMAT="votabletd" POS="350.000395191457,15.9571125164442"
				SIZE="0.00001"
				_DBOPTIONS_DIR="DESC" REQUEST="queryData" MAXREC="5"
				>s/ssap.xml</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertEqual(row['ssa_pubDID'],
					'ivo://org.gavo.dc/~?califa/datadr3/COMB/UGC12519.COMB-75-47')
				self.assertAlmostEqual(row['ssa_specstart'], 3.701e-07)
				self.assertAlmostEqual(row['ssa_dateObs'], 55830.9910027067)
				self.assertAlmostEqual(row['dej2000'], 15.9571125164442)
				self.assertTrue(row['accref'].endswith(
					'califa/datadr3/COMB/UGC12519.COMB-75-47'))
				self.assertAlmostEqual(row['ssa_timeExt'], 2700.0)
				self.assertEqual(row['ssa_dstitle'],
					'CALIFA UGC12519 COMB-75-47')
			</code>
		</regTest>

		<regTest title="califa single spectrum generation works for COMB">
			<url httpHonorRedirects="True"
				>/getproduct/califa/datadr3/COMB/UGC12519.COMB-67-11</url>
			<code>
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(row['spectral'], 5017.0)
				self.assertAlmostEqual(row['flux'], 0.00326692801900208)
				self.assertAlmostEqual(row['error'], 0.0051887105)
			</code>
		</regTest>

		<regTest title="califa single spectrum generation works for V1200">
			<url httpHonorRedirects="True"
				>/getproduct/califa/datadr3/V1200/UGC12519.V1200-01-38</url>
			<code>
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(row['spectral'], 3650.0)
				self.assertAlmostEqual(row['flux'], -0.08278895169496536)
				self.assertAlmostEqual(row['error'], 0.4010688)
			</code>
		</regTest>
		
		<regTest title="califa spectrum datalink has cube link">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/COMB/UGC12519.COMB-75-47"
				>sdl/dlmeta</url>
			<code>
				rows = self.getVOTableRows()
				rowsPassed = 0
				for row in rows:
					if row["semantics"]=="#progenitor":
						self.assertEqual(row["access_url"].split("/q3/")[1],
							"dl/dlmeta?ID=ivo%3A//org.gavo.dc/~%3Fcalifa/datadr3/COMB/UGC12519.COMB.rscube.fits")
						rowsPassed += 1
				self.assertEqual(rowsPassed, 1)
			</code>
		</regTest>

		<regTest title="califa spectrum datalink has correct parameter metadata">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/V1200/UGC12519.V1200-75-47"
				>sdl/dlmeta</url>
			<code>
				self.assertXpath("v:RESOURCE[@type='meta']/v:GROUP[@name='inputParams']"
					"/v:PARAM[@name='BAND']/v:VALUES/v:MIN",
					{"value": EqualingRE(r"3.65\\d*e-07")})
				self.assertXpath("v:RESOURCE[@type='meta']/v:GROUP[@name='inputParams']"
					"/v:PARAM[@name='BAND']/v:VALUES/v:MAX",
					{"value": EqualingRE(r"4.8\\d*e-07")})
			</code>
		</regTest>

	</regSuite>

	<regSuite id="cube">
		<regTest title="califa cube datalink instance looks right">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/V500/UGC12519.V500.rscube.fits"
				>dl/dlmeta</url>
			<code>
				from gavo import votable
				import io

				data, metadata = votable.load(io.BytesIO(self.data))
				rows = list(sorted(metadata.iterDicts(data), key=lambda d:
					(d["semantics"], d["access_url"])))

				bySemantics = {}
				for row in rows:
					if row["error_message"]:
						self.fail("Fault declared in Datalink: %s"%row["error_message"])

					if row["semantics"] in ("#counterpart", "#old-version", "#this"):
						self.assertEqual(row["content_qualifier"], "#cube")

					bySemantics.setdefault(
						(row["access_url"] and row["access_url"].split("/", 3)[-1],
							row["semantics"]), []
							).append(row)

				row = bySemantics[(u'califa/q3/dl/dlmeta?ID=ivo%3A//org.gavo.dc/~%3Fcalifa/datadr3/COMB/UGC12519.COMB.rscube.fits',
				"#counterpart")][0]
				self.assertEqual(row["content_type"],
					'application/x-votable+xml;content=datalink')
				self.assertEqual(row["description"],
					'This cube, larger coverage lower resolution high-SNR')

				row = bySemantics[(u'califa/q3/dl/dlmeta?ID=ivo%3A//org.gavo.dc/~%3Fcalifa/datadr3/V1200/UGC12519.V1200.rscube.fits',
				"#counterpart")][0]
				self.assertEqual(row["content_type"],
					'application/x-votable+xml;content=datalink')
				self.assertEqual(row["description"],
					'This cube, smaller coverage higher resolution')

				row = bySemantics[(None, u'#proc')][0]
				self.assertNotEqual(row["service_def"], None)

				row = bySemantics[(None, u'#proc')][0]
				self.assertNotEqual(row["service_def"], None)

				row = bySemantics[(u'califa/q3/dl/dlget?ID=ivo%3A//org.gavo.dc/~%3Fcalifa/datadr3/V500/UGC12519.V500.rscube.fits', u'#this')][0]
				self.assertEqual(row["content_type"], "application/fits")
				self.assertEqual(row["description"], "The full dataset.")

				for r in rows:
					self.assertEqual(r["ID"],
						"ivo://org.gavo.dc/~?califa/datadr3/V500/UGC12519.V500.rscube.fits")

				self.assertXpath(
					"//v:RESOURCE[2]/v:GROUP[@name='inputParams']/v:PARAM[@name='DEC']", {
						"ucd": "pos.eq.dec",
						"unit": "deg",
						"xtype": "interval",
						"arraysize": "2"})
				self.assertXpath(
					"//v:RESOURCE[2]/v:GROUP[@name='inputParams']/v:PARAM[@name='RA']"
						"/v:DESCRIPTION", {
					None: "The longitude coordinate"})
				self.assertXpath(
					"//v:RESOURCE[2]/v:GROUP[@name='inputParams']/v:PARAM[@name='BAND']"
						"/v:VALUES/v:MIN", {
						"value": "3.749e-07"})
			</code>
		</regTest>

		<regTest title="califa cube datalink retrieves data">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/V500/UGC12519.V500.rscube.fits"
				PIXEL_1="4 7"
				PIXEL_2="23 27"
				PIXEL_3="250 255"
				>dl/dlget</url>
			<code>
				self.assertHasStrings(
					"BITPIX  =                  -32",
					"NAXIS2  =                    5",
					"CDELT3  =                  2.0",
					"H79")
			</code>
		</regTest>

		<regTest title="califa cube datalink circle and band">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/V500/UGC12519.V500.rscube.fits"
				CIRCLE="350.01 15.95 0.002"
				BAND="5e-7 5.01e-7"
				>dl/dlget</url>
			<code>
				self.assertHasStrings(
					"BITPIX  =                  -32",
					"NAXIS1  =                   15",
					"NAXIS3  =                    7",
					b"\\x0a\\x48\\x3c\\xad\\x75\\xab")
			</code>
		</regTest>

		<regTest title="califa cube datalink shows old data">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/V1200/UGC12519.V1200.rscube.fits"
				>dl/dlmeta</url>
			<code>
				oldVersions, onBigServer = [], False
				for row in self.getVOTableRows():
					if row["semantics"]=="#old-version":
						oldVersions.append(row["access_url"].split("/califa/")[1])
						self.assertEqual(row["content_qualifier"], "#cube")
						if not row["access_url"].startswith("http://local"
								) and not row["access_url"].startswith("http://victor"):
							onBigServer = True
				self.assertTrue('q2/dl/dlmeta?ID=ivo%3A//org.gavo.dc/~'
					'%3Fcalifa/datadr2/UGC12519.V1200.rscube.fits'
					in oldVersions, "Califa DR2 missing")
				
				if onBigServer:
					self.assertEqual(len(oldVersions), 2,
						"Califa old data data missing")
			</code>
		</regTest>

				<regTest title="califa cube soda declaration has expected parameters">
			<url ID="ivo://org.gavo.dc/~?califa/datadr3/V500/UGC12519.V500.rscube.fits"
				>dl/dlmeta</url>
			<code>
				self.assertXpath(
					"//v:RESOURCE[2]/v:GROUP/v:PARAM[@name='BAND']", {
						"xtype": "interval",
						"ucd": "em.wl"})
			</code>
		</regTest>
	</regSuite>

	<regSuite title="DaCHS asyncdl" sequential="True" id="asynctest">
		<!-- this is a DaCHS integration test for async datalink -->
		<regTest title="Create asyncdl job">
			<url httpMethod="POST"
				 ID="ivo://org.gavo.dc/~?califa/datadr3/V500/UGC12519.V500.rscube.fits"
				CIRCLE="350.01 15.95 0.002"
				BAND="-Inf 4.1e-7"
				>dl/dlasync</url>
			<code>
				self.assertHTTPStatus(303)
				self.pointNextToLocation("/phase")
				self.assertHeader("location",
					EqualingRE(".*/califa/q3/dl/dlasync/.*"))
			</code>
		</regTest>

		<regTest title="Start asyncdl job">
			<url httpMethod="POST" PHASE="RUN">overridden in previous test</url>
			<code>
				self.assertHTTPStatus(303)
				self.assertHeader("location",
					EqualingRE(".*/califa/q3/dl/dlasync/.*"))
				self.pointNextToLocation()
			</code>
		</regTest>

		<regTest title="Asyncdl job runs">
			<url>overriden in the previous test</url>
			<code>
			<![CDATA[
				try:
					self.assertHasStrings("<uws:phase>EXECUTING</uws:phase>")
				except AssertionError:
					self.assertHasStrings("<uws:phase>COMPLETED</uws:phase>")
				else:
					import time; time.sleep(2) # with a little bit of luck,
						# this might be enough for the job to finish, and we won't need
						# to do the evil loop in the next test (that will poison our
						# test counts and is evil in other ways, too)
				self.followUp.url.content_ = self.url.httpURL
			]]></code>
		</regTest>

		<regTest title="Asyncdl job finishes">
			<url>overridden in the previous test</url>
			<!-- ok, this is evil: I'm returning myself as the next test until
			the job (which should usually be fairly quick) goes out of executing.
			-->
			<code><![CDATA[
				if not hasattr(self, "realFollowUp"):
					self.realFollowUp = self.followUp

				if b"<uws:phase>EXECUTING</uws:phase>" in self.data:
					import time
					time.sleep(2)
					self.followUp = self

				else:
					self.followUp = self.realFollowUp
					self.assertHasStrings("<uws:phase>COMPLETED</uws:phase>")
					self.followUp.url.content_ = self.url.httpURL+"/results/result"
			]]></code>
		</regTest>

		<regTest title="asyncdl product written">
			<url>overridden by previous test</url>
			<code>
				self.assertHasStrings("SIMPLE  =                    T",
					"NAXIS1  =                   15",
					"NAXIS3  =                  177",
					b"\\xbd\\xd6\\x38\\x4c\\x3d\\xb5\\xdc\\xd9")
			</code>
		</regTest>
	</regSuite>
</resource>
