<?xml version="1.0" encoding="utf-8"?>
<!-- definition of the position-related interfaces (and later SCS fragments) -->

<resource resdir="__system" schema="dc">
	<STREAM id="splitPosIndex">
		<doc>
			Adds an index over the separate long and lat columns.
			This is currently implemented using q3c; DaCHS will move
			to pgsphere entirely at some point. RDs using this will not
			need an update then.  This will by default cluster according
			to the index.  In the rare cases when you don't want this,
			add a cluster="False" in the FEED element.

			Since long and lat may be expressions, this will not automatically
			declare index/columns.  You should therefore pass in a
			comma-separated list of column names in the columns attribute
			in order to inform clients of the index.

			Examples::

				&lt;FEED source="//scs#splitPosIndex" long="ra" lat="dec"
					columns="ra,dec"/>

				&lt;FEED source="//scs#splitPosIndex"
					long="long(ssa_location)" lat="lat(ssa_location)"
					columns="ssa_location"/>
		</doc>
		<DEFAULTS cluster="True" columns=""/>
		<index name="splitpos_\\tablename" cluster="\cluster">
			<columns>\columns</columns>
			q3c_ang2ipix(\long,\lat)
		</index>
	</STREAM>

	<STREAM id="q3cIndexDef">
		<doc>
			Definition of a q3c index over the main position of a table.
		</doc>
		<index name="q3c_\\tablename" cluster="True" kind="q3c">
			<columns>\\nameForUCDs{pos.eq.ra;meta.main|POS_EQ_RA_MAIN}, \\nameForUCDs{pos.eq.dec;meta.main|POS_EQ_DEC_MAIN}</columns>
			q3c_ang2ipix(\\nameForUCDs{pos.eq.ra;meta.main|POS_EQ_RA_MAIN}, \\nameForUCDs{pos.eq.dec;meta.main|POS_EQ_DEC_MAIN})
		</index>
	</STREAM>

	<mixinDef id="q3cindex">
		<doc>
			A mixin adding an index to the main equatorial positions.

			In new RDs, use pgs-pos-index instead; we'd like to stop q3c support
			at some point.

			This is what you usually want if your input data already has
			"sane" (i.e., ICRS or at least J2000) positions or you convert
			the positions manually.

			You have to designate exactly one column with the ucds pos.eq.ra;meta.main
			pos.eq.dec;meta.main, respectively.  These columns receive the
			positional index.

			This will fail without the q3c extension to postgres.
		</doc>
		<lateEvents>
			<FEED source="//scs#q3cIndexDef"/>
		</lateEvents>
	</mixinDef>

	<STREAM id="spoint-index-def">
		<doc>
			Definition of an index over spoint(ra, dec); give this
			parameters long and lat naming the corresponding columns (in degrees).
			
			Or use the spoint-index mixin.

			This will by default cluster according to the index.  In the rare cases
			when you don't want this, add a cluster="False" in the FEED element.

			This needs to be replayed within a table (or something that
			defines a tablename macro).
		</doc>
		<DEFAULTS cluster="True" columns=""/>
		<index name="spoint_\\tablename" cluster="\cluster" kind="spoint"
			method="GIST">
			<columns>\ra,\dec</columns>
			spoint(radians(\ra),radians(\dec))
		</index>
	</STREAM>

	<mixinDef id="pgs-pos-index">
		<doc>
			A mixin adding a pgsphere index to the main spherical position for
			tables with separate RA and Dec columns.

			You have to designate exactly one column with the ucds pos.eq.ra;meta.main
			pos.eq.dec;meta.main, respectively.  These columns receive the
			positional index.

			This should be used instead of q3cindex on new tables; it's
			a bit slower than q3c, but it's less funky, too.

			If you'd like an index on other sorts of long/lat pairs, see the
			//scs#spoint-index-def STREAM.
		</doc>
		<lateEvents>
			<FEED source="spoint-index-def"
				ra="\nameForUCDs{pos.eq.ra;meta.main}"
				dec="\nameForUCDs{pos.eq.dec;meta.main}"/>
		</lateEvents>
	</mixinDef>

	<outputTable id="instance">
		<!-- this is the prototype from which SCSCore makes its distance
		columns -->
		<outputField id="distCol"
			name="_r" type="double precision"
			unit="deg" ucd="pos.angDistance"
			tablehead="Dist."
			description="Distance to cone center"
			select="'NaN'::REAL"
			verbLevel="10"
			displayHint="displayUnit=arcsec"/>
	</outputTable>

	<condDesc>
		<!-- common setup for the SCS-related condDescs -->
		<phraseMaker id="scsUtils">
			<setup id="scsSetup">
				<par key="raColName" description="Name of the column containing
					the longitude (default is to determine by UCD)">None</par>
				<par key="decColName" description="Name of the column containing
					the latitude (default is to determine by UCD)">None</par>
				<code>
					from gavo.protocols import scs

					def getRADec(inPars, sqlPars):
						"""tries to guess coordinates from inPars.

						(for human SCS condition).
						"""
						pos = inPars["hscs_pos"]
						try:
							return base.parseCooPair(pos)
						except ValueError:
							data = base.caches.getSesame("web").query(pos)
							if not data:
								raise base.ValidationError("%s is neither a RA,DEC"
								" pair nor a simbad resolvable object"%
								inPars["hscs_pos"], "hscs_pos")
							return float(data["RA"]), float(data["dec"])

					def genQuery(td, inPars, outPars):
						"""returns the query fragment for this cone search.
						"""
						return scs.getRadialCondition(td,
							"%%(%s)s"%base.getSQLKey("RA", inPars["RA"], outPars),
							"%%(%s)s"%base.getSQLKey("DEC", inPars["DEC"], outPars),
							"%%(%s)s"%base.getSQLKey("SR", inPars["SR"], outPars),
							raColName, decColName)
				</code>
			</setup>
		</phraseMaker>
	</condDesc>

	<condDesc id="protoInput" required="True">
		<inputKey name="RA" type="double precision" unit="deg" ucd="pos.eq.ra"
			description="Right Ascension (ICRS decimal)" tablehead="Alpha (ICRS)"
			multiplicity="single"
			std="True">
			<property name="onlyForRenderer">scs.xml</property>
		</inputKey>
		<inputKey name="DEC" type="double precision" unit="deg" ucd="pos.eq.dec"
			description="Declination (ICRS decimal)" tablehead="Delta (ICRS)"
			multiplicity="single"
			std="True">
			<property name="onlyForRenderer">scs.xml</property>
		</inputKey>
		<inputKey name="SR" type="real" unit="deg" description="Search radius"
			multiplicity="single"
			tablehead="Search Radius" std="True">
			<property name="onlyForRenderer">scs.xml</property>
		</inputKey>
		<phraseMaker id="scsPhrase" name="scsSQL">
			<setup original="scsSetup"/>
			<code>
				yield genQuery(core.queriedTable, inPars, outPars)
			</code>
		</phraseMaker>
	</condDesc>

	<condDesc id="humanInput" combining="True" joiner="AND">
		<inputKey id="hscs_pos"
			name="hscs_pos" type="text"
			multiplicity="single"
			description= "Coordinates (as h m s, d m s or decimal degrees), or SIMBAD-resolvable object" tablehead="Position/Name">
			<property name="notForRenderer">scs.xml</property>
		</inputKey>
		<inputKey id="hscs_sr"
			name="hscs_sr" description="Search radius in arcminutes"
			multiplicity="single"
			tablehead="Search radius">
			<property name="notForRenderer">scs.xml</property>
		</inputKey>
		<phraseMaker original="scsUtils" id="humanSCSPhrase" name="humanSCSSQL">
			<code>
				if inPars["hscs_pos"] is None:
					return
				if inPars["hscs_sr"] is None:
					raise base.ValidationError("If you query for a position,"
						" you must give a search radius", "hscs_sr")

				ra, dec = getRADec(inPars, outPars)
				try:
					sr = float(inPars["hscs_sr"])/60.
				except ValueError: # in case we're not running behind forms
					raise gavo.ValidationError("Not a valid float", "hscs_sr")
				inPars = {"RA": ra, "DEC": dec, "SR": sr}
				yield genQuery(core.queriedTable, inPars, outPars)
			</code>
		</phraseMaker>
	</condDesc>

	<STREAM id="makeSpointCD">
		<doc><![CDATA[
			This builds a cone search condDesc for Web forms for an spoint column.

			To define it, say something like::

				<FEED source="//scs#makeSpointCD"
					tablehead="Position observed"
					matchColumn="ssa_location"/>

			This is also used in ``<condDesc buildFrom="(some spoint col)"/>``
		]]></doc>

		<!-- this should be forbidden in untrusted RDs since it's easy to
		do python code or SQL injection using this.  To mitigate it,
		we'd need some input validation for (specific) macro arguments
		or a notion of "unsafe" streams.  Well, right before untrusted
		DaCHS RDs actually start to get swapped, I'll do either of these. -->

		<condDesc>
			<inputKey name="pos_\matchColumn" type="text"
				multiplicity="single"
				description= "Coordinates (as h m s, d m s or decimal degrees),
					or SIMBAD-resolvable object" tablehead="\tablehead">
			</inputKey>
			<inputKey name="sr_\matchColumn"
				multiplicity="single"
				description="Search radius in arcminutes"
				unit="arcmin"
				tablehead="Search radius for \tablehead">
			</inputKey>
			<phraseMaker>
				<setup>
					<code>
						from gavo.protocols import scs
					</code>
				</setup>
				<code><![CDATA[
					ra, dec = scs.parseHumanSpoint(inPars["pos_\matchColumn"],
						"pos_\matchColumn")
					try:
						sr = float(inPars["sr_\matchColumn"])/60.
					except ValueError: # in case we're not running behind forms
						raise gavo.ValidationError("Not a valid float", "sr_\matchColumn")
					yield "%s <@ scircle(%%(%s)s, %%(%s)s)"%("\matchColumn",
						base.getSQLKey("pos",
							pgsphere.SPoint.fromDegrees(ra, dec), outPars),
						base.getSQLKey("sr", sr/180*math.pi, outPars))
				]]></code>
			</phraseMaker>
		</condDesc>
	</STREAM>

	<STREAM id="coreDescs">
		<doc><![CDATA[
			This stream inserts three condDescs for SCS services on tables with
			pos.eq.(ra|dec).main columns; one producing the standard SCS RA,
			DEC, and SR parameters, another creating input fields for human
			consumption, and finally MAXREC.
		]]></doc>
		<condDesc original="//scs#humanInput"/>
		<condDesc original="//scs#protoInput"/>
	</STREAM>

</resource>
