<resource schema="obsform">
	<meta name="creationDate">2021-12-03T14:19:57Z</meta>
	<meta name="schema-rank">20</meta>

	<meta name="title">\getConfig{web}{sitename} Obscore Form</meta>
	<meta name="description" format="rst">
		This is a form-based service allowing users to run simple
		queries against \getConfig{web}{sitename}'s ivoa.obscore table, with
		some nods to users looking of data near the optical waveband.
		For serious use, the `obscore table`_ should be queried through
		TAP.

		Note that queries without spatial constraints may take time out.  You
		probably ought to switch to async TAP if you really want to run such
		queries.

		.. _obscore table: /tableinfo/ivoa.obscore?tapinfo=true
	</meta>
	<meta name="subject">virtual-observatories</meta>

	<meta name="creator">\metaString{publisher}</meta>

	<meta name="source">\getConfig{web}{serverURL}</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Archive</meta>

	<LOOP>
		<codeItems>
			with base.getTableConn() as conn:
				for row in conn.query("SELECT spatial, temporal, spectral"
					" FROM dc.rdmeta"
					" WHERE sourcerd='__system__/obscore'"):

					if None in row:
						# If any part of coverage is missing, skip the whole thing
						return

					yield {
						"spatial": row[0],
						"temporal": " ".join(str(v) for v in row[1]),
						"spectral": " ".join(str(v) for v in row[2])}
		</codeItems>
		<events>
			<coverage>
				<spatial>\spatial</spatial>
				<temporal>\temporal</temporal>
				<spectral>\spectral</spectral>
			</coverage>
		</events>
	</LOOP>

	<service id="web" allowed="form">
		<meta name="shortName">obscore web</meta>
		<publish render="form" sets="local,ivo_managed"/>

		<dbCore queriedTable="//obscore#ObsCore">
			<FEED source="//scs#makeSpointCD"
				tablehead="Position/Name"
				matchColumn="spoint(RADIANS(s_ra), RADIANS(s_dec))"
				/>

			<condDesc buildFrom="calib_level"/>

			<condDesc>
				<inputKey original="obs_collection"
					tablehead="Collection" showItems="10"/>
			</condDesc>

			<condDesc buildFrom="instrument_name"/>

			<condDesc>
				<inputKey name="BAND" type="vexpr-float"
					unit="Angstrom" ucd="em.wl"
					tablehead="Wavelength"
					description="Wavelength covered by the dataset">
					<LOOP>
						<codeItems>
							from gavo import base
							with base.getTableConn() as conn:
								limits = dict((r[0], r[1:]) for r in
									conn.query("SELECT column_name, min_value, max_value"
										" FROM dc.simple_col_stats"
										" WHERE tablename='ivoa.ObsCore'"
										"   AND column_name in ('em_min', 'em_max')"))
								if limits:
									yield {"obscore_emmin":
											utils.formatFloat(float(limits["em_min"][0])*1e10),
										"obscore_emmax":
											utils.formatFloat(float(limits["em_max"][1])*1e10)}
						</codeItems>
						<events>
							<property name="placeholder"
								>\obscore_emmin ..  \obscore_emmax</property>
						</events>
					</LOOP>
				</inputKey>
				<phraseMaker>
					<setup imports="gavo.svcs.vizierexprs, gavo.svcs">
						<code>
							obscore = parent.parent.queriedTable
							minCol = svcs.InputKey.fromColumn(
								obscore.getColumnByName("em_min"),
								inputUnit="Angstrom")
							maxCol = svcs.InputKey.fromColumn(
								obscore.getColumnByName("em_max"),
								inputUnit="Angstrom")
						</code>
					</setup>
					<code>
						try:
							tree = vizierexprs.parseNumericExpr(inPars[inputKeys[0].name])
						except utils.ParseException as msg:
							raise base.ValidationError(
								f"Bad VizieR syntax: {msg}", "BAND")
						res = vizierexprs.NumericIntervalFlattener(
							).getSQLFor(tree, (minCol, maxCol), outPars)
						yield res
					</code>
				</phraseMaker>
			</condDesc>

			<condDesc>
				<inputKey name="TIME" type="vexpr-date"
					ucd="time.epoch"
					tablehead="Obs. time"
					description="Time covered by the observation.">
					<LOOP>
						<codeItems>
							from gavo import base, stc
							with base.getTableConn() as conn:
								limits = dict((r[0], r[1:]) for r in
									conn.query("SELECT column_name, min_value, max_value"
										" FROM dc.simple_col_stats"
										" WHERE tablename='ivoa.ObsCore'"
										"   AND column_name in ('t_min', 't_max')"))
								if limits:
									yield {"obscore_tmin":
											stc.mjdToDateTime(float(limits["t_min"][0])
												).date().isoformat(),
										"obscore_tmax":
											stc.mjdToDateTime(float(limits["t_max"][1])
												).date().isoformat()}
						</codeItems>
						<events>
							<property name="placeholder"
								>\obscore_tmin ..  \obscore_tmax</property>
						</events>
					</LOOP>
				</inputKey>
				<phraseMaker>
					<setup imports="gavo.svcs.vizierexprs, gavo.svcs">
						<code>
							obscore = parent.parent.queriedTable
							minCol = svcs.InputKey.fromColumn(
								obscore.getColumnByName("t_min"))
							maxCol = svcs.InputKey.fromColumn(
								obscore.getColumnByName("t_max"))
						</code>
					</setup>
					<code>
						try:
							tree = vizierexprs.parseDateExprToMJD(inPars[inputKeys[0].name])
						except utils.ParseException as msg:
							raise base.ValidationError(
								f"Bad VizieR syntax: {msg}", "BAND")

						res = vizierexprs.NumericIntervalFlattener(
							).getSQLFor(tree, (minCol, maxCol), outPars)
						yield res
					</code>
				</phraseMaker>
			</condDesc>
		</dbCore>

		<outputTable>
			<LOOP listItems="obs_collection obs_title t_min t_max"><events>
				<outputField original="\item"/>
			</events></LOOP>

			<outputField original="s_ra" displayHint="type=hms"/>
			<outputField original="s_dec" displayHint="type=dms"/>
			<outputField original="s_fov" displayHint="displayUnit=arcsec,sf=0"/>

			<LOOP listItems="em_min em_max"><events>
				<outputField original="\item" displayHint="displayUnit=Angstrom"/>
			</events></LOOP>

			<LOOP listItems="access_estsize access_url">
				<events><outputField original="\item"/></events>
			</LOOP>

			<outputField name="dimensions"
					tablehead="Dim"
					description="Pixel Dimensions, space x space x time x spectrum x
						polarization"
					select="array[s_xel1, s_xel2, t_xel, em_xel, pol_xel]">
				<formatter>
					return "x".join(str(s) if s else "1" for s in data)
				</formatter>
			</outputField>
		</outputTable>
	</service>

	<regSuite title="Obscore form regression">
		<!-- These are specific to the GAVO data centre.  If other people
			actually run this on their sites, contact Markus and we'll figure out
			some way to make these locally configurable. -->
		<regTest title="Obsform responds to positional query in time.">
			<url parSet="form" hscs_pos="262.2 -37.5" hscs_sr="2.0"
				_TIMEOUT="2">web</url>
			<code>
				self.assertHasStrings("&lt;p&gt;Matched: ")
			</code>
		</regTest>

		<regTest title="Obsform responds to spectral query in time.">
			<url parSet="form" BAND="20 .. 50" _TIMEOUT="5">web</url>
			<code>
				self.assertHasStrings("&lt;p&gt;Matched: ")
			</code>
		</regTest>

		<regTest title="Obsform responds to temporal query in time.">
			<url parSet="form" TIME="1910-01-01 .. 1911-01-01"
				_TIMEOUT="4">web</url>
			<code>
				self.assertHasStrings("&lt;p&gt;Matched: ")
			</code>
		</regTest>

		<regTest title="Obsform responds to collection/Caliblevel in time.">
			<url parSet="form" calib_level="2" obs_collection="RASS"
				_TIMEOUT="4">web</url>
			<code>
				self.assertHasStrings("&lt;p&gt;Matched: ")
			</code>
		</regTest>

	</regSuite>
</resource>
