<resource schema="bgds2" resdir=".">
	<macDef name="pubDIDBase">ivo://\getConfig{ivoa}{authority}/~?\rdId/</macDef>
	
	<meta name="creationDate">2024-07-23T08:00:00Z</meta>
	<meta name="schema-rank">20</meta>

	<meta name="title">BGDS DR2 time series</meta>

	<meta name="isNewVersionOf"
		ivoId="ivo://org.gavo.dc/bgds/l/ssa">BGDS light curves DR1</meta>
	<meta name="doi">10.21938/dChYyzuCGA00rsfzfQ8v:Q</meta>

	<meta name="description" format="rst">
		From the Bochum Galactic Disk Survey, time series have been obtained in the
		r and i bands on an (up to) nightly basis. Depending on the field, the time
		series contain up to more than 300 nights over 9 years. Each measurement in
		r and i represents the averaged flux over 10 minutes of observation
		(from 9 averaged 10s images). Additionally, intermittent measurements
		in Johnson UVB, Sloan z and the narrowbands OIII, NB, Halpha and SII
		have been recorded as well.

		The Bochum Galactic Disk Survey is a project to monitor the stellar content
		of the Galactic disk in a 6 degree wide stripe centered on the Galactic
		plane. The data has been recorded from September 2010 to September 2019 in
		Sloan r and i simultaneously with the Robotic Bochum Twin Telescope (RoBoTT)
		at the Universitaetssternwarte Bochum near Cerro Armazones in the Chilean
		Atacama desert. It contains measurements of about 2x10^7 stars over nine
		years.

		The source images are available from ivo://org.gavo.dc/bgds/q/sia.
	</meta>

	<meta name="_longdoc" format="rst">
The time series from BGDS DR2 by default come as IVOA Spectral Data Model
compliant VOTables.  If you what plain text instead, add
``&amp;format=txt`` to the dlget URIs.  The columns you get back then
are epoch in MJD, magnitude and magnitude error in mag, and a link to
a cutout showing where the measurement came from.

Accessing and Plotting Light Curves from pyVO
---------------------------------------------

Let us briefly illustrate how to access and plot the GDS data with
pyVO_.  First, some basic definitions that we will need in the code below;
there is no need to understand this code in detail::


		import pyvo as vo
		import matplotlib.pyplot as plt
		import numpy as np

		service = vo.dal.TAPService("http://dc.g-vo.org/tap")

		def get_lc(ra, dec, rad, filter):
				"""Returns a light curve given by mjds and mags
				around the coordinates ra and dec.
				"""
				# Search for the object coordinates in the metadata table:
				id_set = service.search("SELECT obs_id"
					" FROM bgds2.ssa_time_series"
					f" WHERE DISTANCE({ra}, {dec}, ra, dec)&lt;{rad}/3600."
					" AND ssa_bandpass like '% " + filter + "%'")
				if len(id_set) >= 1:
					# Use the ID from the first result in the time series metadata table
					# (there is only one result for the examples here, but sources in
					# overlapping observation fields can have up to four light curves
					# per filter) to receive the corresponding light curve from the
					# light curve table:
					lc_set = service.search(
						"SELECT mjds, mags FROM bgds2.lc_all"
						"  WHERE obs_id = '" + id_set.getcolumn("obs_id")[0] + "'")
					mjds = lc_set.getcolumn("mjds")[0]
					mags = lc_set.getcolumn("mags")[0]
					return mjds, mags

				else:
					print(f"There is no GDS light curve for the filter {filter}"
						 f" in a radius of {rad}'' around the coordinates {ra} {dec}.")

		def plot_ts(r_mjds, r_mags, i_mjds, i_mags):
				"""Plot r' and i' light curves given by r_mjds, i_mjds and r_mags, i_mags.
				"""
				plt.plot(r_mjds, r_mags, 'ro', markersize=3.0)
				plt.plot(i_mjds, i_mags, 'ko', markersize=3.0)
				plt.gca().invert_yaxis()
				plt.xlabel('MJD (d)')
				plt.ylabel('Magnitude')
				plt.show()
				return

		def fold_lc(mjds, mags, p):
				"""Fold a light curve given by mjds and mags with a period p.
				"""
				# Fold the dates mjds by the period p:
				mjds_fold = mjds % p / p

				# Sort mjds and mags by the phase:
				idx_fold = np.argsort(mjds_fold)
				mjds_fold = mjds_fold[idx_fold]
				mags_fold = mags[idx_fold]
				return mjds_fold, mags_fold

		def find_p(mjds, mags, min_p, max_p, p_step):
				"""Find the period p in a given range from min_p to max_p in steps of p_step
				with minimal theta.
				"""
				theta_list = []
				p_candidates = np.arange(min_p, max_p, p_step)

				for j in range(len(p_candidates)):
					mjds_fold, mags_fold = fold_lc(mjds, mags, p_candidates[j])
					# Shift the magnitudes of the folded light curve mags_fold by one step to
					# compute differences:
					mags_shift = [mags_fold[1:len(mags_fold)]]
					mags_shift = np.append(mags_shift,mags_fold[0])
					theta = np.sum((mags_fold - mags_shift)**2)
					theta_list.append(theta)

				theta_list = np.asarray(theta_list)
				min_ind = np.argmin(theta_list)
				p = p_candidates[min_ind]
				return p, p_candidates, theta_list

		def plot_theta(p_candidates, theta_list):
				"""Plot theta (given by a list/array theta_list) in dependence of the period
				given by a an array p_candidates.
				"""
				plt.plot(p_candidates, theta_list, 'ko', markersize=2.5)
				plt.xlim([np.min(p_candidates), np.max(p_candidates)])
				plt.xlabel('$P$ (d)')
				plt.ylabel('$\\\\theta$')
				plt.show()
				return

		def plot_phase(r_mjds, r_mags, i_mjds, i_mags, p):
				"""Fold r' and i' light curves given by r_mjds, i_mjds and r_mags, i_mags
				with a period p and plot two phases.
				"""
				r_mjds_fold, r_mags_fold = fold_lc(r_mjds, r_mags, p)
				i_mjds_fold, i_mags_fold = fold_lc(i_mjds, i_mags, p)
				plt.plot([r_mjds_fold, [x+1 for x in r_mjds_fold]],
								[r_mags_fold, r_mags_fold], 'ro', markersize=3.0)
				plt.plot([i_mjds_fold, [x+1 for x in i_mjds_fold]],
								[i_mags_fold, i_mags_fold], 'ko', markersize=3.0)
				plt.axvline(1, color = 'gray', linestyle='-', markersize=0.3)
				plt.xlim([0.,2.])
				plt.gca().invert_yaxis()
				plt.xlabel('Phase')
				plt.ylabel('Magnitude')
				plt.title('$P = %.4f\\,\\mathrm{d}$' % p)
				plt.show()
				return

		def get_multi_wl_phot(ra, dec, rad):
				"""Returns an entry from the multi-wavelength photometry catalogue
				(matched Gaia DR3 ID gdr3_id, arrays for magnitudes multi_wl_mags and
				spectral flux density in mJy multi_wl_fluxes in UBVr'i'z', and an estimated
				spectral type spt from UBV photometry) around the coordinates ra and dec.
				"""
				wl_cols = ["u", "b", "v", "r", "i", "z"]
				multi_wl_set = service.search(
					"SELECT gdr3_id, u_med_mag, b_med_mag, v_med_mag,"
					" r_med_mag, i_med_mag, z_med_mag, u_flux, b_flux, v_flux,"
					" r_flux, i_flux, z_flux, spt FROM bgds2.matched"
					f" WHERE DISTANCE({ra}, {dec}, ra, dec)&lt;{rad}/3600.")

				if len(multi_wl_set) >= 1.:
					multi_wl_mags = np.zeros(6)
					multi_wl_fluxes = np.zeros(6)

					for n in range(len(wl_cols)):
							multi_wl_mags[n] = multi_wl_set.getcolumn(wl_cols[n] + "_med_mag")[0]
							multi_wl_fluxes[n] = multi_wl_set.getcolumn(wl_cols[n] + "_flux")[0]

					gdr3_id = str(multi_wl_set.getcolumn("gdr3_id")[0])
					spt = multi_wl_set.getcolumn("spt")[0]
					return gdr3_id, multi_wl_mags, multi_wl_fluxes, spt

				else:
					print(f"There is no GDS photometry in a radius of {rad}"
						f" around the coordinates {ra} {dec}.")

		def plot_sed(multi_wl_fluxes):
				"""Plot the spectral flux density in UBVr'i'z' in dependence of the
				filter wavelength.
				"""
				wls = np.array([365, 433, 550, 623, 764, 906])
				filters = ["U", "B", "V", "r'", "i'", "z'"]
				plt.plot(wls, multi_wl_fluxes, 'ko', markersize=5.0)

				for n in range(len(multi_wl_fluxes)):
					plt.text(wls[n]+7, multi_wl_fluxes[n]-8, filters[n])

				plt.xlabel('Wavelength (nm)')
				plt.ylabel('Flux (mJy)')
				plt.show()
				return

		def calc_abs_mag(mag, plx):
				"""Calculate the absolute magnitude abs_mag from the relative magnitude mag
				and the parallax plx in milli-second of arc.
				"""
				# Approximation of the distance dist in parsec:
				dist = 1000./plx

				abs_mag = mag - 5.*(np.log10(dist) - 1.)
				return abs_mag


.. _pyVO: https://github.com/astropy/pyvo

Light curves
------------

The GDS contains light curves in up to 10 filters per observation field, among
which the r’ and i’ light curves contain the largest numbers of data points. We
can take a look at the r’ and i’ light curves of a star, e.g., the highly
variable *V352 Nor*::

		r_mjds, r_mags = get_lc(241.71551, -52.07636, 1., "r")
		i_mjds, i_mags = get_lc(241.71551, -52.07636, 1., "i")
		plot_ts(r_mjds, r_mags, i_mjds, i_mags)

The result is:

.. figure:: /\rdId/tsdl/static/V352_Nor_lc.png
		:alt: A scatterplot of two fairly parallel sets of red and black dots over
				more than 2000 days, showing steep maxima up to 7 mag, reaching down to
				16 mag.  Prominent gaps in the coverage are visible.


Period determination
--------------------

The periods of many periodic variables in the GDS are still unknown or
uncertain. Short periods are not obvious when looking at the
(unfolded) light curve, as is the case for the eclipsing binary EH Mon::

		r_mjds, r_mags = get_lc(103.03654, -7.06476, 1., "r")
		i_mjds, i_mags = get_lc(103.03654, -7.06476, 1., "i")
		plot_ts(r_mjds, r_mags, i_mjds, i_mags)

.. figure:: /\rdId/tsdl/static/EH_Mon_lc.png
		:alt: A scatterplot, again in two bands; most dots are about 13, but there
				are five steep drops down to 15.

Among the numerous options for an automated period classification, here we
will use the `Lafler-Kinman algorithm`_.

.. _Lafler-Kinman algorithm: https://ui.adsabs.harvard.edu/abs/1965ApJS...11..216L/abstract

The light curve is folded by trial periods and the sum of squares θ of the
differences between two magnitude measurements of adjacent phase is calculated.
Basically, θ measures how smooth a folded light is when folded by a trial
period.  Usually, θ is minimized when a light curve is folded by a multiple of
the correct period, therefore, test values with local minima in θ are the best
candidates for the period, in order from low to high.

::

		r_p, r_p_candidates, r_theta_list = find_p(r_mjds, r_mags, 1., 15., 1e-4)
		i_p, i_p_candidates, i_theta_list = find_p(i_mjds, i_mags, 1., 15., 1e-4)

		# Exemplarily plot theta for the filter i':
		plot_theta(i_p_candidates, i_theta_list)

.. figure:: /\rdId/tsdl/static/EH_Mon_theta.png
		:alt: A plot of θ versus the period, ranging from 1 to 15 days.
				there is a broad, dense black field across the plot, with sharp
				dips at about 3.6 days and its harmonics.

::

		# The final period p is the average between period results from the
		# r' and i' light curves:
		p = (r_p + i_p) / 2.
		plot_phase(r_mjds, r_mags, i_mjds, i_mags, p)

.. figure:: /\rdId/tsdl/static/EH_Mon_phase.png
		:alt: a scatter plot over the phase in two bands, showing fairly parallel
				primary and secondary dips.

Multi-wavelength catalogue
--------------------------

Median magnitudes and light curves are not only available separately for
filters and fields, but there is also a multi-wavelength photometry catalogue
that is matched with Gaia DR3 sources. The filter magnitudes can be noticeably
effected by reddening; in this case, the roughly estimated spectral types from
the GDS UBV photometry might be more useful. For
*Gaia DR3 5334136458661121280*, median magnitudes in UBVr’i’z’ are available::


		gdr3_id, multi_wl_mags, multi_wl_fluxes, spt = get_multi_wl_phot(
				175.11591, -62.02608, 1.)
		print("Gaia DR3 ID: " + gdr3_id + ", Spectral type from UBV photometry: " + spt)
		print(f"U = {np.round(multi_wl_mags[0], 3)}, "
				f"B = {np.round(multi_wl_mags[1], 3)}, "
				f"V = {np.round(multi_wl_mags[2], 3)}, "
				f"r' = {np.round(multi_wl_mags[3], 3)}, "
				f"i' = {np.round(multi_wl_mags[4], 3)}, "
				f"z' = {np.round(multi_wl_mags[5], 3)}")
		plot_sed(multi_wl_fluxes)


The result is::

		Gaia DR3 ID: 5334136458661121280, Spectral type from UBV photometry: K5
		U = 14.15, B = 12.996, V = 11.781, r' = 11.366, i' = 10.853, z' = 10.551

.. figure:: /\rdId/tsdl/static/Gaia_DR3_5334136458661121280_sed.png
		:alt: A sparse scatterplot of calibrated flux over wavelength, showing
			an almost linear growth of dots labelled with the filter designations
			between 380 and 900 nm.

The match with Gaia DR3 allows to directly get information from the
Gaia DR 3 catalogue like the Gaia DR parallaxes to compute absolute
magnitudes::

		gdr3_set = service.search(
				"SELECT parallax FROM gaia.dr3lite WHERE source_id=" + gdr3_id)
		plx = gdr3_set.getcolumn("parallax")[0]
		multi_wl_abs_mags = np.zeros(6)

		for n in range(len(multi_wl_abs_mags)):
				multi_wl_abs_mags[n] = calc_abs_mag(multi_wl_mags[n], plx)

		print(f"M_U = {np.round(multi_wl_abs_mags[0], 3)}, "
				f"M_B = {np.round(multi_wl_abs_mags[1], 3)}, "
				f"M_V = {np.round(multi_wl_abs_mags[2], 3)}, "
				f"M_r' = {np.round(multi_wl_abs_mags[3], 3)}, "
				f"M_i' = {np.round(multi_wl_abs_mags[4], 3)}, "
				f"M_z' = {np.round(multi_wl_abs_mags[5], 3)}")

This gives::

		M_U = 8.957, M_B = 7.803, M_V = 6.588, M_r' = 6.173, M_i' = 5.66, M_z' = 5.358
	</meta>

	<meta name="Continues">
		<meta name="ivoId">ivo://org.gavo.dc/bgds/l/ssa"</meta
		>BGDS DR1 Lightcurves</meta>

	<meta name="subject">surveys</meta>
	<meta name="subject">galaxy-planes</meta>
	<meta name="subject">milky-way-galaxy</meta>
	<meta name="subject">variable-stars</meta>
	<meta name="subject">broad-band-photometry</meta>
	<meta name="subject">time-domain-astronomy</meta>

	<meta name="creator">Blex, J.; Hackstein, M.; Westhues, C.; Ramolla, M.; Demleitner, M.; Fein, C.; Chini, R.</meta>
	<meta name="instrument">Robotic Bochum Twin Telescope (RoBoTT)</meta>
	<meta name="facility">Universitätssternwarte Bochum near Cerro
		Armazones</meta>

	<meta name="source">2026AN....34770087B</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Survey</meta>
	<meta name="productTypeServed">timeseries</meta>

	<meta name="copyright" format="rst">
		If you use GDS data, please cite
		:bibcode:`2026AN....34770087B`.
	</meta>

	<meta name="coverage">
		<meta name="waveband">Optical</meta>
	</meta>

	<coverage>
			<!-- copied from bgds/q -->
			<spectral>1.777e-19 6.621e-19</spectral>
			<temporal>55439 58751.4</temporal>
			<spatial>3/336,338,450-451,461,472,591-592,613,615,621,651-652,659,662-663 4/1301,1303,1358,1376-1377,1379-1380,1382,1794,1816,1818-1819,1822-1823,1829,1840-1841,1892,1894,1897,2327,2333,2335,2357-2359,2376,2378,2400,2402,2408,2492-2493,2495,2528,2530,2536,2538,2591,2599,2601-2603,2612-2614,2616,2634-2635,2643,2646-2647,2656-2657,2688-2690</spatial>
	</coverage>

	<!-- The rough data structure is:  there are photcats of fields
	and filters with too few observations to merit proper lightcurves,
	and there are lccats that have the same columns as photcats
	(medphot-columns) but additionally LCMJD, LCMAG, and LCMAGERR.

	To untangle that, we have common phot_<filter> tables with a
	proper primary key (obs_id, our creation), and then lc_<filter>
	tables that only have the array-valued columns from lccat where
	available -->

	<!-- ####################################################
		median photometry from both photcat and lccat -->

	<!-- filters with photcats -->
	<macDef name="photfilters"
		>b,	Johnson B, B_j,         em.opt.b
			u,	Johnson U, U_j,         em.opt.U
			v,	Johnson V, V_j,         em.opt.V
			z,	SDSS z',		z_s,        em.opt.I
	</macDef>

	<!-- filters with lccats (that's the full set of bands)-->
	<macDef name="lcfilters"
		>i, SDSS i', i_s,            em.opt.I
			r, SDSS r', r_s,            em.opt.r
			ha,     Astrodon Halpha, Ha,    em.line.halpha
			nb, Astrodon NB, NB,        em.line
			oiii, Astrodon OIII, OIII,  em.line.oiii
			sii, Astrodon SII, SII,     em.line
			\photfilters
	</macDef>

	<macDef name="phot_all_cols">
		obs_id, ra, dec, field, amp, flux, err_flux, nobs, gdr3_id
	</macDef>

	<STREAM id="obs-id-def">
		<column name="obs_id" type="text"
			ucd="meta.id;meta.main"
			description="Main identifier of this observation (a single
				object may have multiple observations in different bands and fields)"
			verbLevel="1"/>
		<primary>obs_id</primary>
	</STREAM>


	<LOOP>
		<csvItems>
			 band, filter, pathspec,     ucd
			\photfilters
		</csvItems>
		<events>
			<table id="phot_\band" onDisk="True" adql="True">
				<mixin>//scs#pgs-pos-index</mixin>
				<meta name="table-rank">100</meta>
				<meta name="description">
					BGDS median photometry in the \filter band.  This is available
					for all objects found in the fields observed.  Median photometry
					in the i' and r' bands is given in the lc_(band) tables together
					with the lightcurves.
				</meta>
				<stc>
					Position ICRS "ra" "dec"
				</stc>

				<FEED source="obs-id-def"/>

				<column name="ra" type="double precision"
					ucd="pos.eq.ra;meta.main"
					description="ICRS right ascension for this object as mean of
						the positions obtained from source extraction."
					verbLevel="1"/>
				<column name="dec" type="double precision"
					ucd="pos.eq.dec;meta.main"
					description="ICRS declination for this object as mean of
						the positions obtained from the source extraction."
					verbLevel="1"/>
				<column name="med_mag_\band"
					unit="mag" ucd="phot.mag;\ucd;stat.median"
					tablehead="〈mag〉"
					description="Median magnitude in \filter."
					verbLevel="5" displayHint="sf=3"/>
				<column name="err_mag_\band"
					unit="mag" ucd="stat.error;phot.mag;\ucd"
					tablehead="Err〈mag〉"
					description="Error in median magnitude in \filter"
					verbLevel="15" displayHint="sf=3"/>
				<column name="amp"
					unit="mag" ucd="phot.mag;\ucd;arith.diff"
					tablehead="Amplitude"
					description="Difference between brightest and weakest observation."
					verbLevel="15" displayHint="sf=3"/>

				<column name="flux"
					unit="mJy" ucd="phot.flux;\ucd;stat.median"
					tablehead="〈flux〉"
					description="Median flux in the \filter band; this is computed
						from the median magnitude based on Landolt standard stars."
					verbLevel="5" displayHint="sf=3"/>
				<column name="err_flux"
					unit="mJy" ucd="stat.error;phot.flux;\ucd"
					tablehead="Err〈flux〉"
					description="Error in median flux in the \filter band"
					verbLevel="15" displayHint="sf=3"/>
				<column name="nobs" type="smallint" required="True"
					ucd="meta.number;obs"
					tablehead="#Obs"
					description="Number of observations of this object"
					verbLevel="25"/>

				<column name="field" type="text"
					ucd="meta.id;obs.field"
					tablehead="Field"
					description="Survey field observed."
					verbLevel="15">
				</column>

				<column name="gdr3_id" type="bigint"
					ucd="meta.id.cross"
					tablehead="GDR3"
					description="Gaia DR3 source_id of this object."
					verbLevel="5">
					<values nullLiteral="-1"/>
				</column>
			</table>

			<data id="import_\band">
				<sources>
					<pattern>data/dr2phot/*_\pathspec\+_photcat*.fits.gz</pattern>
				</sources>

				<directGrammar id="booster-\band"
					cBooster="res/photlcbooster.c"
					type="fits">
					<mapKeys>
						ra:RA, dec:DEC,
						err_mag_\band: MAGERR,
						amp: AMP,
						flux: FLUX, err_flux: FLUXERR,
						nobs: NOBS,
						gdr3_id: Gaia_DR3_ID
					</mapKeys>
				</directGrammar>

				<make table="phot_\band"/>
<!--		replaced by C booster:
				<fitsTableGrammar/>

				<make table="phot_\band">
					<rowmaker>
						<simplemaps>
							ra:RA, dec:DEC,
							err_mag_\band: MAGERR,
							amp: AMP,
							flux: FLUX, err_flux: FLUXERR,
							nobs: NOBS,
						</simplemaps>
						NOTE: obs id also defined independently in lc_all rowmaker
						<map key="obs_id"
							>"BGDS-%s-\band-%.7f%+.7f"%(@fieldid, @RA, @DEC)</map>
						<var key="fieldid"
							>re.search("GDS_([^_]*)", \\inputRelativePath).group(1)</var>
						<map key="med_mag_\band" source="MAG(MED)"/>
						<map key="field" source="fieldid"/>
					</rowmaker>
				</make>-->
			</data>
		</events>
	</LOOP>

	<!-- ###################################
		lightcurves from lccat* -->

	<LOOP>
		<csvItems>
			band, filter, pathspec,     ucd
			\lcfilters
		</csvItems>
		<events>
			<table id="lc_\band" onDisk="True" adql="true">
				<mixin>//scs#pgs-pos-index</mixin>
				<mixin>//scs#q3cindex</mixin>
				<meta name="table-rank">90</meta>
				<meta name="description">
					BGDS DR2 lightcurves in the \filter band. Lightcurves are
					available in i' and r' for all fields and partially
					in U, B, V, z, OIII, NB, Halpha and SII if they have
					enough observations.
				</meta>

				<index columns="time_min"/>
				<index columns="time_max"/>
				<index columns="time_avg"/>
				<index columns="ssa_bandpass"/>
				<index columns="ssa_specstart"/>
				<index columns="ssa_specend"/>
				<index columns="obs_id">('\\pubDIDBase' || obs_id)</index>

				<FEED source="obs-id-def"/>
				<column original="phot_u.gdr3_id"/>
				<column original="phot_u.ra"/>
				<column original="phot_u.dec"/>
				<column original="phot_u.field"/>

				<column name="med_mag"
					unit="mag" ucd="phot.mag;\ucd;stat.median"
					tablehead="〈mag〉"
					description="Median magnitude in \filter."
					verbLevel="5" displayHint="sf=3"/>
				<column name="err_mag"
					unit="mag" ucd="stat.error;phot.mag;\ucd"
					tablehead="Err〈mag〉"
					description="Error in median magnitude in \filter"
					verbLevel="15" displayHint="sf=3"/>

				<column original="phot_u.amp" ucd="phot.mag;em.opt;arith.diff"/>

				<column name="flux"
					unit="mJy" ucd="phot.flux;\ucd;stat.median"
					tablehead="〈flux〉"
					description="Median flux in the \filter band; this is computed
						from the median magnitude based on Landolt standard stars."
					verbLevel="5" displayHint="sf=3"/>
				<column name="err_flux"
					unit="mJy" ucd="stat.error;phot.flux;\ucd"
					tablehead="Err〈flux〉"
					description="Error in median flux in the \filter band"
					verbLevel="15" displayHint="sf=3"/>

				<column original="phot_u.nobs"/>
				<column name="var" type="smallint"
					ucd="meta.code;src.var"
					tablehead="Var"
					description="1 if the light curves was classified as variable.
					 0 otherwise. The variability analysis is done only for r and i."
					verbLevel="1">
					<values nullLiteral="-1"/>
				</column>

				<column name="mjds" type="double precision[154]"
					unit="d" ucd="time.epoch"
					tablehead="T[]"
					description="An array containing the time values of the time series"/>
				<column name="mags" type="real[154]"
					unit="mag" ucd="phot.mag;\ucd"
					tablehead="mag[]"
					description="An array containing the \filter magnitude
						for the times in mjd"/>
				<column name="mag_errs" type="real[154]"
					unit="mag" ucd="stat.error;phot.mag"
					tablehead="Err(mag)[]"
					description="An array containing per-epoch errors
						for the mags array"/>
				<column name="time_min" type="double precision" xtype="mjd"
					unit="d" ucd="time.epoch;stat.min"
					tablehead="1st Obs"
					description="First timestamp in time series (MJD Topocentric UTC)"
					verbLevel="1"/>
				<column name="time_max" type="double precision" xtype="mjd"
					unit="d" ucd="time.epoch;stat.max"
					tablehead="Last Obs"
					description="Last timestamp in time series (MJD Topocentric UTC)"
					verbLevel="1"/>
				<column name="time_avg" type="double precision"
					unit="d" ucd="time.epoch;stat.mean"
					tablehead="&lt;Obs>"
					description="Mean epoch of the observations"
					verbLevel="25"/>
				<column original="//ssap#instance.ssa_bandpass"/>
				<column original="//ssap#instance.ssa_specstart"/>
				<column original="//ssap#instance.ssa_specend"/>
				<column original="//ssap#instance.ssa_specmid"/>
				<column original="//ssap#instance.ssa_aperture"
					description="Angular diameter of aperture. The aperture is
						elliptical and adaptively scaled for each source using the
						Kron radius (1980ApJS...43..305K), as implemented in
						SourceExtractor (1996A&amp;AS..117..393B). The mean FWHM of all
						sources for each filter is given."/>
			</table>
		</events>
	</LOOP>

	<LOOP>
		<csvItems>
			band, filter, pathspec, ucd
			\lcfilters
		</csvItems>
		<events>
			<data id="import-lc\band" recreateAfter="make-phot">
				<sources pattern="data/dr2curves/*\pathspec\+_lccat.fits.gz"/>
				<fitsTableGrammar/>

				<make table="lc_\band">
					<rowmaker id="make-with-time-series" idmaps="*">
						<simplemaps>
							nobs: NOBS,
							ra: RA,
							dec: DEC,
							err_mag: MAGERR,
							flux: FLUX,
							err_flux: FLUXERR,
							amp: AMP,
							field: Field,
						</simplemaps>
						<var key="fieldid"
							>re.search("GDS_([^_]*)", \\inputRelativePath).group(1)</var>
						<apply name="remove_invalid_measurements">
							<code>
								@LCMJD = @LCMJD[@LCMAG!=99.0]
								@LCMAGERR = @LCMAGERR[@LCMAG!=99.0]
								@LCMAG = @LCMAG[@LCMAG!=99.0]
							</code>
						</apply>

						<apply name="clean_filter" procDef="//procs#dictMap">
							<bind key="default">base.NotGiven</bind>
							<bind key="key">"Filter"</bind>
							<bind key="mapping">{
								"i": "i_s",
								"r": "r_s",
								"b": "B_j",
								"u": "U_j",
								"v": "V_j",
								"z": "z_s",
								"nb": "NB",
								"ha": "Ha",
								"oiii": "OIII",
								"sii": "SII",
							}</bind>
						</apply>

						<apply name="interpret_filter">
							<setup><code>
							BANDMAP = {
								"i_s": ("SDSS i'", 6.65e-7, 8.39e-7, 7.44e-7, 1.5e-3, "i"),
								"r_s": ("SDSS r'", 5.42e-7, 6.99e-7, 6.12e-7, 1.3e-3, "r"),
								"B_j": ('Johnson B', 3.70e-7, 5.50e-7, 4.41e-7, 1.4e-3, "b"),
								"U_j": ('Johnson U', 3.00e-7, 3.90e-7, 3.46e-7, 1.7e-3, "u"),
								"V_j": ('Johnson V', 4.80e-7, 7.30e-7, 5.50e-7, 1.5e-3, "v"),
								"z_s": ("SDSS z'", 7.78e-7, 11.2e-7, 8.96e-7, 1.6e-3, "z"),
								"NB": ('Astrodon NB', 6.42e-7, 6.48e-7, 6.45e-7, 1.3e-3, "nb"),
								"Ha": ('Astrodon Halpha', 6.53e-7, 6.56e-7, 6.60e-7, 1.6e-3, "ha"),
								"OIII": ('Astrodon OIII', 4.97e-7, 5.04e-7, 5.00e-7, 1.2e-3, "oiii"),
								"SII": ('Astrodon SII', 6.69e-7, 6.75e-7, 6.71e-7, 1.4e-3, "sii"),}
							</code></setup>
							<code>
								@ssa_bandpass, @ssa_specstart, @ssa_specend, \
									@ssa_specmid, @ssa_aperture, @shortband = BANDMAP[@Filter]
							</code>
						</apply>

						<!-- obs_id is also defined independently in the photlcbooster,
							and any changes here must be reproduced there -->
						<map key="obs_id"
							>"BGDS-%s-%s-%.7f%+.7f"%(@fieldid, @shortband, @RA, @DEC)</map>
						<map key="mjds" source="LCMJD"/>
						<map key="mags" source="LCMAG"/>
						<map key="mag_errs" source="LCMAGERR"/>
						<map key="time_min">@LCMJD[0]</map>
						<map key="time_max">@LCMJD[-1]</map>
						<map key="time_avg">sum(@LCMJD)/len(@LCMJD)</map>
						<map key="med_mag" source="MAG(MED)"/>
						<map key="gdr3_id" source="Gaia_DR3_ID"
							nullExcs="ValueError,KeyError"/>
						<map key="var">int(vars.get("VARIABLE") or 0)</map>
					</rowmaker>
				</make>
			</data>
		</events>
	</LOOP>


	<!-- #################################################
		Joined median photometry from photcat and lccat -->

	<macDef name="photall_description">
			Extracted sources from the Bochum Galactic
			Disk Survey. We provide median photometry in U, B, V, z, r, and i
			broadbands as well as the narrow bands OIII, NB, Halpha and SII
			for particular fields. Note that sources in different bands are
			not matched. Also, sources sitting in the regions imaged in
			multiple fields have not been matched even within one band. BGDS
			light curves are available in i and r for all fields and partially
			in U, B, V, z, OIII, NB, Halpha and SII in a separate SSAP service
			and via TAP.

			For median photometry matched to Gaia DR3 objects, see the
			bgds2.matched table and the matchedphot service.
	</macDef>

	<table id="phot_all" adql="True" onDisk="True" namePath="phot_u">
		<meta name="description">
			\photall_description
		</meta>
		<meta name="table-rank">15</meta>
		<property name="forsceStats">True</property>
		<index metaOnly="True" kind="spoint" name="spoint_fake"
			columns="ra,dec"/>

		<column name="band_name" type="text"
			ucd="meta.id;instr.filter"
			tablehead="Filter"
			description="The band the median photometry is given for."
			verbLevel="1"/>
		<column original="obs_id"/>
		<column original="ra"/>
		<column original="dec"/>
		<column original="amp"/>
		<column original="field"/>
		<column original="med_mag_u" name="med_mag" ucd="phot.mag"
			description="Median magnitude in the band given by filter"/>
		<column original="err_mag_u" name="err_mag"
			ucd="stat.error;phot.mag" description="Error in med_mag"/>
		<column original="flux" ucd="phot.flux"
			description="Median flux in the band given by the filter column;
				this is computed from the median magnitude based on Landolt standard
				stars."/>
		<column original="err_flux" ucd="stat.error;phot.flux"/>
		<column original="nobs"/>
		<column original="gdr3_id"/>
		
		<viewStatement>
		CREATE VIEW \curtable AS (SELECT \colNames FROM (
										SELECT 'Johnson B' as band_name,
														med_mag_b as med_mag, err_mag_b as err_mag,
														\phot_all_cols
										FROM \schema.phot_b
						UNION ALL
										SELECT 'Johnson B' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_b
						UNION ALL
										SELECT 'Johnson U' as band_name,
														med_mag_u as med_mag, err_mag_u as err_mag,
														\phot_all_cols
										FROM \schema.phot_u
						UNION ALL
										SELECT 'Johnson U' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_u
						UNION ALL
										SELECT 'Johnson V' as band_name,
														med_mag_v as med_mag, err_mag_v as err_mag,
														\phot_all_cols
										FROM \schema.phot_v
						UNION ALL
										SELECT 'Johnson V' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_v
						UNION ALL
										SELECT 'SDSS z''' as band_name,
														med_mag_z as med_mag, err_mag_z as err_mag,
														\phot_all_cols
										FROM \schema.phot_z
						UNION ALL
										SELECT 'SDSS z''' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_z
						UNION ALL
										SELECT 'SDSS i''' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_i
						UNION ALL
										SELECT 'SDSS r''' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_r
						UNION ALL
										SELECT 'Astrodon Halpha' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_ha
						UNION ALL
										SELECT 'Astrodon NB' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_nb
						UNION ALL
										SELECT 'Astrodon OIII' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_oiii
						UNION ALL
										SELECT 'Astrodon SII' as band_name,
														med_mag, err_mag,
														\phot_all_cols
										FROM \schema.lc_sii
			) as allbands)
		</viewStatement>
	</table>

	<data id="make-phot" recreateAfter="make-lcall">
		<make table="phot_all"/>
	</data>


	<service id="medphot" allowed="form,scs.xml">
		<publish render="scs.xml" sets="ivo_managed"/>
		<publish render="form" sets="local,ivo_managed"/>
		<meta name="shortName">BGDS2 median pho</meta>
		<meta name="title">BGDS DR2 Median Photometry Cone Search</meta>
		<meta name="description">
			\photall_description
		</meta>

		<meta name="doi">10.21938/vjJw6sq0gmS5psH9uM5_NA</meta>
		<meta name="_related" title="Median photometry matched to Gaia DR3"
			>\internallink{\rdId/matchedphot/info}</meta>

		<meta>
			testQuery.ra:	145.2949318
			testQuery.dec: -56.932663
			testQuery.sr:	0.0001
		</meta>

		<scsCore queriedTable="phot_all">
			<FEED source="//scs#coreDescs"/>
		 	<condDesc buildFrom="med_mag"/>
		 	<condDesc buildFrom="err_mag"/>
		 	<condDesc buildFrom="amp"/>
		 	<condDesc>
		 		<inputKey original="band_name">
		 			<values id="bandnames">
						<LOOP>
							<csvItems>
								band, filter, pathspec,     ucd
								\lcfilters
							</csvItems>
							<events>
								<option>\filter</option>
							</events>
						</LOOP>
		 			</values>
		 		</inputKey>
		 	</condDesc>
		</scsCore>
	</service>


	<!-- ##############################################
		############# All light curves -->

	<table id="lc_all" onDisk="True" adql="True" namePath="lc_i">
		<meta name="description">A union of all BGDS2 light curves.
			Note that the magnitudes here are in different bands.</meta>
		<meta name="table-rank">10</meta>
		<index metaOnly="True" kind="spoint" name="spoint_fake" columns="ra,dec"/>

		<column original="phot_all.band_name"/>
	
		<column original="med_mag"
			ucd="phot.mag;em.opt"
			description="Median magnitude in the band given in the band_name column"/>
		<column original="err_mag"
			ucd="stat.error;phot.mag;em.opt"
			description="Error in med_mag"/>
		<column original="flux"
			ucd="phot.flux;em.opt"
			description="Median flux in the band given in the band_name column.
				This is computed from the median magnitude based on Landolt standard
				stars."/>
		<column original="flux"
			ucd="stat.error;phot.flux;em.opt"
			description="Error in flux"/>
		<column original="mags"
			ucd="phot.mag;em.opt"
			description="An array containing the magnitudes for band_name
				and the times in mjd"/>
		<column original="mag_errs"
			ucd="stat.error;phot.mag;em.opt"
			description="Errors in mags"/>
	
		<LOOP listItems="obs_id gdr3_id ra dec field amp nobs var mjds
			time_min time_max time_avg ssa_bandpass ssa_specstart ssa_specend
			ssa_specmid ssa_aperture">
			<events>
				<column original="\item"/>
			</events>
		</LOOP>

		<!-- nasty hack alert: band_name happens to be first in \colNames,
			and thus we get away with specifying the band literal as we
			do in the viewStatement.  Cough -->
		<viewStatement>
			CREATE VIEW \curtable AS (
					SELECT 'SDSS i''' as \colNames FROM \schema.lc_i
				UNION ALL
					SELECT 'SDSS r''' as \colNames FROM \schema.lc_r
				UNION ALL
					SELECT 'SDSS z''' as \colNames FROM \schema.lc_z
				UNION ALL
					SELECT 'Astrodon Halpha' as \colNames FROM \schema.lc_ha
				UNION ALL
					SELECT 'Astrodon NB' as \colNames FROM \schema.lc_nb
				UNION ALL
					SELECT 'Astrodon OIII' as \colNames FROM \schema.lc_oiii
				UNION ALL
					SELECT 'Astrodon SII' as \colNames FROM \schema.lc_sii
				UNION ALL
					SELECT 'Johnson B' as \colNames FROM \schema.lc_b
				UNION ALL
					SELECT 'Johnson U' as \colNames FROM \schema.lc_u
				UNION ALL
					SELECT 'Johnson V' as \colNames FROM \schema.lc_v
				)
		</viewStatement>
	</table>

	<data id="make-lcall" recreateAfter="make-ssa">
		<make table="lc_all"/>
	</data>

	<!-- ##############################################
		############# Photometry measurements matched to GDR3 objects -->

	<macDef name="matched_description">
			Median photometry in U, B, V, z, r, and i
			broadbands as well as the narrow bands OIII, NB, Halpha and SII
			for particular objects; all measurements have been matched to the
			nearest Gaia DR3 object.

			For the raw measurements, see the bgds2.phot_all table and the
			medphot service.
	</macDef>

	<table id="matched" onDisk="True" mixin="//scs#q3cindex" adql="True">
		<meta name="table-index">10</meta>
		<meta name="title">BGDS2 Median Photometry Matched to Gaia DR3</meta>
		<meta name="description">
			\matched_description
		</meta>

		<index columns="gdr3_id"/>
		<column name="ra" type="double precision"
			unit="deg" ucd="pos.eq.ra;meta.main"
			tablehead="RA"
			description="ICRS RA of the object, obtained as the median of
				all RA measurements of all r and i measurements.  If there
				are no r and i positions, this is the median of the positions
				from all filters."
			verbLevel="1"/>
		<column name="dec" type="double precision"
			unit="deg" ucd="pos.eq.dec;meta.main"
			tablehead="Dec"
			description="ICRS Declination of the object, obtained as the median of
				all declination measurements of all r and i measurements.  If there
				are no r and i positions, this is the median of the positions
				from all filters."
			verbLevel="1"/>
		<column original="lc_i.gdr3_id"/>
		<LOOP>
			<csvItems>
				band, filter, pathspec,     ucd
			  \lcfilters
			</csvItems>
			<events passivate="True">
				<FEED source="//procs#witherror" name="\band\+_med_mag"
					unit="mag" ucd="phot.mag;\ucd"
					tablehead="m_\band"
					description="Median magnitude in \filter"
					verbLevel="10"/>
				<FEED source="//procs#witherror" name="\band\+_flux"
					unit="mJy" ucd="phot.flux;\ucd"
					tablehead="Flux \band"
					description="Median flux in \filter"
					verbLevel="10"/>
			</events>
		</LOOP>
		<column original="lc_i.var"
			description="1 if at least one of the r or i light curves
				was classified as variable. 0 otherwise. The variability
				analysis is done only for r and i."/>
		<column name="spt" type="text"
			ucd="src.sptype"
			tablehead="Spect."
			description="Spectral type estimated via UBV photometry, the
				reddening path (from 1956ApJ...124..367H), and the fiducial
				colours per spectral type (from Tab. 1-2 in
				1970A&amp;A.....4..234F). This is only given where UBV
				photometry is available and the reddening path as a unique
				intersection with the spectral type."
			verbLevel="1"/>
		<column original="gdr3_id" ucd="meta.id;meta.main"/>
	</table>

	<data id="import-match">
		<sources pattern="data/BGDS_matched_cat/*.txt"/>
		<csvGrammar names="ra, dec, gdr3_id,
			u_med_mag,err_u_med_mag,u_flux,err_u_flux,
			b_med_mag,err_b_med_mag,b_flux,err_b_flux,
			v_med_mag,err_v_med_mag,v_flux,err_v_flux,
			r_med_mag,err_r_med_mag,r_flux,err_r_flux,
			i_med_mag,err_i_med_mag,i_flux,err_i_flux,
			z_med_mag,err_z_med_mag,z_flux,err_z_flux,
			oiii_med_mag,err_oiii_med_mag,oiii_flux,err_oiii_flux,
			sii_med_mag,err_sii_med_mag,sii_flux,err_sii_flux,
			nb_med_mag,err_nb_med_mag,nb_flux,err_nb_flux,
			ha_med_mag,err_ha_med_mag,ha_flux,err_ha_flux,
			var, spt"/>
		<make table="matched">
			<rowmaker idmaps="*">
				<map key="spt">@spt.strip() or None</map>
			</rowmaker>
		</make>
	</data>

	<service id="matchedphot" allowed="form,scs.xml">
		<publish render="scs.xml" sets="ivo_managed"/>
		<publish render="form" sets="local,ivo_managed"/>
		
		<meta name="shortName">BGDS2 median pho</meta>
		<meta name="title">BGDS DR2 Matched Photometry Cone Search</meta>
		<meta name="description">
			\matched_description
		</meta>
		<meta name="_related" title="Unmatched median photometry"
			>\internallink{\rdId/medphot/info}</meta>
		<meta name="doi">10.21938/whiHSDPrL:m8cI6AWZ:CFg</meta>

		<meta>
			testQuery.ra:	145.2949318
			testQuery.dec: -56.932663
			testQuery.sr:	0.0001
		</meta>

		<scsCore queriedTable="matched">
			<FEED source="//scs#coreDescs"/>
		 	<condDesc buildFrom="r_med_mag"/>
		 	<condDesc buildFrom="i_med_mag"/>
		 	<condDesc buildFrom="var"/>
		</scsCore>
	</service>


	<!-- ######################################################
			 SSA over time series       ########################### -->

	<table id="ssa_time_series" onDisk="true" adql="True" namePath="lc_all">
		<meta name="description">
			This table contains metadata about the photometric time
			series from BGDS in IVOA SSA format.	The actual data is
			available through a datalink service or in the lc_all table.
			</meta>
		<property name="forceStats">True</property>

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

		<mixin
			fluxUCD="phot.flux.density;em.wl"
			fluxUnit="s**-1"
			spectralUnit="m"
			spectralUCD="em.wl"
			>//ssap#mixc</mixin>

		<mixin
			calibLevel="2"
			tMax="time_max"
			tMin="time_min"
			tResolution="100"
			ra="ra"
			dec="dec"
			em_xel="1"
			t_xel="ssa_length"
			createDIDIndex="True"
			>//obscore#publishSSAPMIXC</mixin>
		
		<stc>
			TimeInterval UTC TOPOCENTER "time_min" "time_max"
		</stc>

		<column original="obs_id"/>
		<column original="time_min"/>
		<column original="time_max"/>
		<column original="amp"/>
		<column original="med_mag"/>
		<column original="field"/>
		<column original="ra"/>
		<column original="dec"/>
		<column original="mjds"/>
		<column original="mags"/>
		<column original="mag_errs"/>
		<column original="gdr3_id"/>
		<column original="var"/>

		<viewStatement>
			CREATE VIEW \curtable AS (SELECT \colNames FROM
				(SELECT
				obs_id,
				amp,
				med_mag,
				field,
				'\getConfig{web}{serverURL}/bgds/l2/tsdl/dlget?ID='||obs_id as accref,
				NULL::text as owner,
				NULL::date as embargo,
				'application/x-votable+xml'::TEXT as mime,
				10000 as accsize,
				'BGDS DR2 ' || ssa_bandpass || ' time series for ' || obs_id as ssa_dstitle,
				NULL::text as ssa_creatorDID,
				'\pubDIDBase' || obs_id as ssa_pubDID,
				NULL::timestamp as ssa_cdate,
				NULL::timestamp as ssa_pdate,
				band_name as ssa_bandpass,
				NULL::text as ssa_cversion,
				field as ssa_targname,
				'survey field' as ssa_targclass,
				NULL::real as ssa_redshift,
				NULL::spoint as ssa_targetpos,
				NULL::real as ssa_snr,
				ssa_aperture,
				spoint(radians(ra), radians(dec)) as ssa_location,
				time_avg as ssa_dateObs,
				time_max-time_min as ssa_timeExt,
				ssa_specmid,
				ssa_specend-ssa_specstart as ssa_specext,
				ssa_specstart,
				ssa_specend,
				nobs as ssa_length,
				'timeseries'::TEXT as ssa_dstype,
				'ivo://org.gavo.dc/org'::TEXT as ssa_publisher,
				'Blex, J. et al'::TEXT as ssa_creator,
				'BGDS DR2 time series'::TEXT as ssa_collection,
				'\metaString{instrument}'::TEXT as ssa_instrument,
				'survey'::TEXT as ssa_datasource,
				'archival'::TEXT as ssa_creationtype,
				'\metaString{source}'::TEXT as ssa_reference,
				NULL::real as ssa_fluxStatError,
				NULL::real as ssa_fluxSysError,
				'ABSOLUTE'::TEXT as ssa_fluxcalib,
				NULL::real as ssa_binSize,
				NULL::real as ssa_spectStatError,
				NULL::real as ssa_spectSysError,
				'ABSOLUTE'::TEXT as ssa_speccalib,
				NULL::real as ssa_specres,
				time_min as time_min,
				time_max as time_max,
				ra,
				dec,
				gdr3_id,
				var,
				mjds as mjds,
				mags as mags,
				mag_errs as mag_errs
				FROM
					\schema.lc_all
				) as src)
		</viewStatement>
	</table>

	<data id="make-ssa" auto="false">
		<make table="ssa_time_series"/>
	</data>

	<service id="tsform" allowed="form">
		<meta name="title">Bochum Galactic Disk Survey (BGDS) DR2 light
			curves browser service</meta>
		<dbCore queriedTable="ssa_time_series">
			<condDesc buildFrom="ssa_location"/>
			<condDesc buildFrom="amp"/>
			<condDesc buildFrom="med_mag"/>
			<condDesc buildFrom="field"/>
			<condDesc>
				<inputKey original="ssa_bandpass">
					<values>
						<option>Astrodon Halpha</option>
						<option>Astrodon NB</option>
						<option>Astrodon OIII</option>
						<option>Astrodon SII</option>
						<option>Johnson B</option>
						<option>Johnson U</option>
						<option>Johnson V</option>
						<option>SDSS i'</option>
						<option>SDSS r'</option>
						<option>SDSS z'</option>
					</values>
				</inputKey>
			</condDesc>
		</dbCore>

		<outputTable>
			<column original="accref" displayHint="type=product"/>
			<FEED source="//ssap#atomicCoords"/>
			<autoCols>ssa_bandpass, med_mag, amp</autoCols>
			<column original="time_min" displayHint="type=humanDate"/>
			<column original="time_max" displayHint="type=humanDate"/>
		</outputTable>
	</service>

	<service id="ssa" allowed="form,ssap.xml">
	 	<meta name="shortName">BGDS TS SSAP</meta>
		<meta name="ssap.dataSource">survey</meta>
		<meta name="ssap.creationType">archival</meta>
		<meta name="ssap.testQuery">MAXREC=1</meta>
		<meta name="ssap.complianceLevel">query</meta>
		<publish render="ssap.xml" sets="ivo_managed"/>
		<publish render="form" sets="ivo_managed,local" service="tsform"/>

		<meta name="title">Bochum Galactic Disk Survey (BGDS) DR2 light
			curves</meta>
		<meta name="description">This service exposes the
			light curves of stars produced by the Bochum Galactic Disk Survey DR2;
			several million light curves are provided in the SDSS i and r bands.
			The lightcurves are published per-band and are also available
			through obscore.</meta>

		<ssapCore queriedTable="ssa_time_series">
			<FEED source="//ssap#hcd_condDescs">
				<PRUNE id="timeCond"/>
			</FEED>
			<condDesc>
				<inputKey name="TIME" type="pql-float"
					unit="d" ucd="time.epoch"
					utype="ssa:Char.TimeAxis.Coverage.Location.Value"
					description="Time covered by the time series"
					multiplicity="forced-single">
					<!-- TODO: enter limits -->
				</inputKey>
				<phraseMaker>
					<code>
					parsed = pql.PQLFloatPar.fromLiteral(
						inPars.get("TIME", None), "TIME")
					yield parsed.getSQLForInterval(
						"time_min", "time_max", outPars)
					</code>
				</phraseMaker>
			</condDesc>
			<condDesc buildFrom="field"/>
		</ssapCore>
	</service>


	<!-- ######################################################
			 building time series -->

	<STREAM id="time-series-template">
		<table id="instance_\band_short">
			<mixin	
				effectiveWavelength="\effective_wavelength"
				filterIdentifier='"\band_human"'
				longitude="@ra"
				latitude="@dec"
				phot_description="BDGS magnitude in \band_human"
				phot_ucd='phot.mag;\band_ucd'
				phot_unit="mag"
				pos_epoch="J2015.5"
				refframe="ICRS"
				refposition="BARYCENTER"
				time0="2400000.5"
				time_description="Barycentric time of observation"
				timescale="TCB"
			>//timeseries#phot-0</mixin>
			<dm>
				(ivoa:Measurement) {
					value: @phot
					statError: @mag_error
				}
			</dm>

			<dm>
				(ds:Dataset) {
					dataProductType: TIMESERIES
					title: @title
					curation:
						(ds:Curation) {
							calibLevel: 1
							publisher:
								(ds:Publisher) {
									name: "GAVO Data Center"
									publisherId: "ivo://org.gavo.dc"
								}
						}
				}
			</dm>

			<param name="obs_id" type="text"
				ucd="meta.id;meta.main"
				description="BGDS id of the observation"/>
			<param name="title" type="text"
				ucd="meta.title;obs"
				description="Publisher-assigned title of the data set"/>
			<param name="ra" type="double precision"
				ucd="pos.eq.ra"
				description="BGDS RA of source object"/>
			<param name="dec" type="double precision"
				ucd="pos.eq.dec"
				description="BGDS Dec of source object"/>
			<param name="filter" type="text"
				ucd="meta.id;instr.filter"
				description="Filter used."/>
			<param name="field" type="text"
				ucd="meta.id;obs.field"
				description="Field observed."/>
			<param name="ssa_aperture" type="double precision"
				unit="deg" ucd="phys.angSize;instr.fov"
				description="Angular diameter of aperture. The aperture is
				elliptical and adaptively scaled for each source using the
				Kron radius (1980ApJS...43..305K), as implemented in
				SourceExtractor (1996A&amp;AS..117..393B). The mean FWHM of all
				sources for each filter is given."/>
			<param original="phot_all.gdr3_id" required="False"/>
			<param original="lc_i.var" required="False"
				ucd="meta.code;src.var"
				description="1 if the light curves was classified as variable.
				0 otherwise. The variability analysis is done only for r and i."/>
			<column name="mag_error"
				unit="mag" ucd="stat.error;phot.mag;\band_ucd"
				description="Error in \band_human magnitude."/>
			<column name="stamp_url" type="text"
				ucd="meta.ref.url"
				tablehead="Image"
				description="Link to a cutout of the source image."/>
		</table>
	</STREAM>

	<LOOP source="time-series-template">
		<csvItems>
			band_short, band_human, band_ucd, effective_wavelength
			i,					SDSS/i,		 em.opt.I, 7.44e-7
			r,					SDSS/r,		 em.opt.R, 6.12e-7
			b,          Johnson/B,  em.opt.B, 4.45e-7
			u,          Johnson/U,  em.opt.U, 3.55e-7
			v,          Johnson/V,  em.opt.V, 5.50e-7
			z,          SDSS/z,     em.opt.I, 9.10e-7
			ha,         Astrodon/Halpha,  spect.line, 6.563e-7
			nb,         Astrodon/NB,  spect.line, 6.45e-7
			sii,        Astrodon/SII,  spect.line, 6.716e-7
			oiii,       Astrodon/OIII,  spect.line, 5.007e-7
		</csvItems>
	</LOOP>

	<data id="make_instance" auto="False">
		<embeddedGrammar>
			<!-- the source token is a TSDescriptor from the tsdl service -->
			<iterator>
				<code>
					epochs, mags, errors = (
						self.sourceToken.row["mjds"],
						self.sourceToken.row["mags"],
						self.sourceToken.row["mag_errs"])

					labels = ["obs_time", "phot", "mag_error"]
					for tup in zip(epochs, mags, errors):
						res = dict(zip(labels, tup))
						res["obs_id"] = self.sourceToken.row["obs_id"]
						yield res
				</code>
			</iterator>

			<pargetter>
				<code>
					with base.getTableConn() as conn:
						res = list(conn.queryToDicts(
							"SELECT * FROM \schema.ssa_time_series WHERE obs_id=%(obs_id)s"
								" LIMIT 2",
							{"obs_id": self.sourceToken.obsId}))
					return res[0]
				</code>
			</pargetter>
		</embeddedGrammar>

		<make table="instance_i">
			<parmaker idmaps="obs_id">
				<map key="title">("BGDS DR2 %(ssa_bandpass)s time series for"
					" the observation %(obs_id)s")%vars</map>
				<map dest="ra">@ssa_location.asDALI()[0]</map>
				<map dest="dec">@ssa_location.asDALI()[1]</map>
				<map dest="field">"-".join(@obs_id.split("-")[1:3])</map>
				<map dest="filter">@ssa_bandpass</map>
			</parmaker>
			<rowmaker idmaps="*">
				<apply name="compute_stamp_url">
					<!-- unfortunately, it is impossible to forsee the file
					name from the data we have here; the dates in the file names
					are only loosely related to the MJD.	Hence, we use a special
					hack on the BGDS datalink service, which also accepts
					ID=MAGIC/field/band/mjd and will choose the best fit in
					the image DB.	Sigh. -->
					<code>
						pars = targetTable.getParamDict()
						imgId = "MAGIC/%s/%s/%f"%(
							pars["field"], pars["filter"], @obs_time)
						@stamp_url = ("http://dc.g-vo.org/bgds/q/dl/dlget?ID="
							+urllib.parse.quote(imgId)
							+"&amp;CIRCLE=%s+%s+%s"%(pars["ra"], pars["dec"], 0.01))
					</code>
				</apply>
			</rowmaker>
		</make>
	</data>

	<service id="tsdl" allowed="dlmeta,dlget,static">
		<meta name="title">BGDS DR2 time series datalink</meta>
		<meta name="description">
			This service produces time series datasets for Bochum
			Galactic Disk Survey lightcurves.
		</meta>
		<property name="staticData">static</property>

		<datalinkCore>
			<descriptorGenerator>
				<setup>
					<code>
						from gavo import svcs

						class TSDescriptor(ProductDescriptor):
							def __init__(self, pubDID):
								# We accept "local" pubDIDs (without ivo://...?), too
								if pubDID.startswith("ivo://"):
									self.pubDID = pubDID
									self.obsId = pubDID.split("?")[-1].split("/")[-1]
								else:
									self.obsId = pubDID
									self.pubDID = '\pubDIDBase'+pubDID

								self.band = self.obsId.split("-")[3]
								self.suppressAutoLinks = True

								with base.getTableConn() as conn:
									res = list(conn.queryToDicts(
										"SELECT * FROM \schema.lc_all"
										" WHERE obs_id=%(obsId)s",
										{"obsId": self.obsId}))
								if not res:
									raise svcs.UnknownURI(
										"BGDS has no lightcurve for %s"%self.obsId)

								self.row = res[0]
					</code>
				</setup>
				<code>
					return TSDescriptor(pubDID)
				</code>
			</descriptorGenerator>

			<metaMaker semantics="#this">
				<code>
					yield descriptor.makeLink(
						makeAbsoluteURL("\rdId/tsdl/dlget?ID=%s"%(descriptor.pubDID)),
						description="BGDS time series for %s."%(
							descriptor.obsId),
						contentType="application/x-votable+xml",
						contentLength=15000,
						contentQualifier="#timeseries")
				</code>
			</metaMaker>

			<metaMaker semantics="#preview">
				<code>
					yield descriptor.makeLink(
						makeAbsoluteURL("\rdId/preview/qp/%s"%(descriptor.obsId)),
						description="Preview for %s"%descriptor.obsId,
						contentType="image/png",
						contentLength="2000")
				</code>
			</metaMaker>

			<metaMaker>
				<code>
				yield MS(InputKey, name="format", type="text",
					description="Requested output format",
					multiplicity="single",
					values=MS(Values,
						options=[
							MS(Option, content_="vodml"),
							MS(Option, content_="txt")]))
				</code>
			</metaMaker>

			<dataFunction>
				<setup>
					<code>
						from gavo import rsc
					</code>
				</setup>
				<code>
					dd = rd.getById("make_instance")
					descriptor.data = rsc.Data.createWithTable(dd,
						rd.getById("instance_"+descriptor.band))
					descriptor.data = rsc.makeData(
						dd,
						data=descriptor.data,
						forceSource=descriptor)
				</code>
			</dataFunction>

			<dataFormatter>
				<code>
					from gavo import formats
					outputFormat = args.get("format") or "vodml"

					return (base.votableType, 	
						formats.getFormatted(outputFormat, descriptor.data))
				</code>
			</dataFormatter>
		</datalinkCore>
	</service>

	<service id="preview" allowed="qp">
		<property name="queryField">obs_id</property>
		<meta name="title">BGDS DR2 previews</meta>
		<meta name="description">
			A service returning PNG thumbnails for time series.	It takes
			the obs id for which to generate a preview from the remaining
			fragments.
		</meta>
		<pythonCore>
			<inputTable>
				<inputKey name="obs_id" type="text"
					tablehead="Obs. Id"
					description="Observation id of the object to create the preview
						for."/>
			</inputTable>
			<coreProc>
				<setup>
					<code>
						from gavo.svcs import UnknownURI
						from gavo.helpers.processing import SpectralPreviewMaker
					</code>
				</setup>
				<code>
					obsId = inputTable.getParam("obs_id")
					# always go through a mapping here or you'll create a SQL injection.
					try:
						srcTable = {
							"r": "bgds2.lc_r",
							"i": "bgds2.lc_i",
							}[obsId.split("-")[3]]
					except KeyError:
						raise UnknownURI("No time series in the %s band here"%
							obsId.split("-")[3])

					with base.getUntrustedConn() as conn:
						res = list(conn.query(
							"SELECT mjds, mags FROM %s WHERE obs_id=%%(obs_id)s"%
								srcTable, {"obs_id": obsId}))
					if not res:
						raise UnknownURI("No time series for obs id %s."%obsId)
					return "image/png", SpectralPreviewMaker.get2DPlot(
						list(zip(*res[0])))
				</code>
			</coreProc>
		</pythonCore>
	</service>

	<regSuite title="BGDS DR2 regression">
		<regTest title="BGDS SCS serves plausible data">
			<url RA="145.2949318"
					DEC="-56.932663" SR="0.0001"
				>medphot/scs.xml</url>
			<code>
				rows = self.getVOTableRows()
				rows.sort(key=lambda r: r["band_name"])
				self.assertTrue(set(r["band_name"] for r in rows).issuperset(
					set(["Johnson V", "SDSS i'", "SDSS z'"])))
				self.assertAlmostEqual(rows[0]["med_mag"], 14.346710205078125)
				self.assertEqual(rows[-1]["obs_id"],
					"BGDS-0941-5551-z-145.2949100-56.9326400")
			</code>
		</regTest>

		<regTest title="BGDS SSAP serves plausible data">
			<url REQUEST="queryData" POS="145.2028193,-56.9319994" SIZE="0.00001"
				field="GDS_0941-5551"
				>ssa/ssap.xml</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertEqual(row["ssa_pubDID"],
					"ivo://org.gavo.dc/~?bgds/l2/BGDS-0941-5551-i-145.2028193-56.9319994")
				self.assertAlmostEqual(row["time_max"], 58253.98748)
				self.assertTrue(row["accref"].endswith(
					"bgds/l2/tsdl/dlget?ID=BGDS-0941-5551-i-145.2028193-56.9319994"))
			</code>
		</regTest>

		<regTest title="BGDS time series form returns data (and links)">
			<url parSet="form" pos_ssa_location="145.2028193,-56.9319994"
				sr_ssa_location="0.1">tsform/form</url>
			<code><![CDATA[
				self.assertHasStrings(
					'bgds/l2/tsdl/dlget?ID=BGDS-0941-5551-i-145.2028193-56.9319994',
					"<td>SDSS i'</td>",
					"16.196")
			]]></code>
		</regTest>

		<regTest title="BGDS time series retrievable (short id).">
			<url ID="BGDS-0941-5551-i-145.2028193-56.9319994"
				>tsdl/dlget</url>
			<code>
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(row["obs_time"], 55934.25169)
				self.assertAlmostEqual(row["phot"], 16.158958)
				self.assertTrue(row["stamp_url"].endswith(
					"ID=MAGIC/0941-5551/SDSS%20i%27/55934.251690&amp;CIRCLE=145.2028193+-56.9319994+0.01"))
				self.assertXpath("//v:GROUP[@name='photcal']"
					"/v:PARAM[@name='filterIdentifier']", dict(
					ucd="meta.id;instr.filter",
					value="SDSS/i"))
			</code>
		</regTest>

		<regTest title="BGDS time series datalink">
			<url ID="ivo://org.gavo.dc/~?bgds/l2/BGDS-0941-5551-i-145.2028193-56.9319994"
				>tsdl/dlmeta</url>
			<code>
				bySemantics = self.datalinkBySemantics()
				self.assertTrue(bySemantics["#this"][0]["access_url"].endswith(
					"l2/tsdl/dlget?ID=ivo://org.gavo.dc/~?bgds/l2/BGDS-0941-5551-i-145.2028193-56.9319994"))
				self.assertEqual(bySemantics["#this"][0]["content_qualifier"],
					"#timeseries")
				self.assertTrue(bySemantics["#preview"][0]["access_url"].endswith(
					"/bgds/l2/preview/qp/BGDS-0941-5551-i-145.2028193-56.9319994"))
			</code>
		</regTest>

		<regTest title="BGDS time series preview gives a reasonable error message
				for a bad band">
			<url>preview/qp/BGDS-0941-5551-b-145.2028193-56.9319994</url>
			<code>
				self.assertHasStrings("No time series in the b band here")
				self.assertHTTPStatus(404)
			</code>
		</regTest>

		<regTest title="BGDS time series preview looks like a PNG">
			<url>preview/qp/BGDS-0941-5551-i-145.2028193-56.9319994</url>
			<code>
				self.assertHasStrings(
					"PNG",
					"27UHs")
					# well, the latter is just binary nonsense and is subject to change
			</code>
		</regTest>

		<regTest title="BGDS matched table present">
			<url parSet="TAP" QUERY="SELECT * FROM  bgds2.matched
				WHERE gdr3_id=5306297133481428096">/tap/sync</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertAlmostEqual(row["v_med_mag"], 17.03499984741211)
				self.assertAlmostEqual(row['err_v_med_mag'], 0.1889999955892563)
				self.assertEqual(row['var'], 0)
			</code>
		</regTest>
	</regSuite>
</resource>
