<resource schema="lamost6">
	<meta name="creationDate">2020-10-16T10:16:41Z</meta>
	<meta name="schema-rank">100</meta>

	<meta name="title">LAMOST DR6 Spectra</meta>
	<meta name="description">
		LAMOST, the The Large Sky Area Multi-Object Fiber Spectroscopic Telescope
		(or Guoshoujing Telescope) is an instrument tailored for producing
		large number of optical medium- and low-resolution spectra.  Here, we
		publish both the medium (MRS) and low (LRS) resolution spectra
		from Data Release 6, http://dr6.lamost.org/v2/, to the Virtual Observatory.
	</meta>

	<meta name="subject">spectroscopy</meta>
	<meta name="subject">surveys</meta>
	<meta name="subject">stars</meta>

	<meta name="creator">National Astronomical Observatories of the
		Chinese Academy of Sciences</meta>
	<meta name="instrument">LAMOST</meta>
	<meta name="facility">CAS, NAO</meta>

	<meta name="source">http://dr6.lamost.org/v2/</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Survey</meta>

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

	<meta name="copyright" format="rst">Guoshoujing Telescope (the Large Sky Area
		Multi-Object Fiber Spectroscopic Telescope LAMOST) is a National Major
		Scientific Project built by the Chinese Academy of Sciences. Funding for
		the project has been provided by the National Development and Reform
		Commission.  LAMOST is operated and managed by the National Astronomical
		Observatories, Chinese Academy of Sciences.
		
		Please also see the `LAMOST Data Policy`_.
		
		.. _LAMOST Data Policy: http://dr.lamost.org/ucenter/doc/lssdp
	</meta>

	<meta name="ssap.dataSource">survey</meta>
	<meta name="ssap.creationType">archival</meta>
	<meta name="productTypeServed">spectrum</meta>
	<meta name="ssap.testQuery">MAXREC=1</meta>

	<STREAM id="catcolumns">
		<doc>Table material for both low-res and med-res spectra.</doc>
		<mixin>//products#table</mixin>
		<mixin>//ssap#plainlocation</mixin>
		<mixin>//ssap#simpleCoverage</mixin>
		<FEED source="//scs#splitPosIndex"
			long="degrees(long(ssa_location))"
			lat="degrees(lat(ssa_location))"
			columns="ssa_location"/>

		<column original="ssa_location"/>
		<column original="ssa_dateObs"/>

		<column name="obsid" type="bigint" required="True"
			ucd="meta.id;obs"
			description="Unique identifier for the observation; LAMOST observations
				typically result in multiple spectra."
			verbLevel="15"/>
		<column name="designation" type="text"
			ucd="meta.id"
			tablehead="Target"
			description="Target Designation (IAU style)"
			verbLevel="15"/>
		<column name="planid" type="text"
			ucd="meta.id;obs.proposal"
			tablehead="Plan"
			description="Identifier of the observation plan"
			verbLevel="25"/>
		<column name="spid" type="smallint" required="True"
			ucd="meta.id;instr"
			tablehead="SG id"
			description="Identifier of the Spectragraph that took the
				source spectrum."
			verbLevel="25"/>
		<column name="fiberid" type="smallint" required="True"
			ucd="meta.id;instr"
			tablehead="Fiber"
			description="Identifier of the fiber used to take the source
				spectrum"
			verbLevel="25"/>

		<column name="ra_obs" type="double precision"
			unit="deg" ucd="pos.eq.ra"
			tablehead="Fiber RA"
			description="ICRS right ascension of the fiber pointing (can be
				different from ra for bright sources)."
			verbLevel="15"/>
		<column name="dec_obs" type="double precision"
			unit="deg" ucd="pos.eq.dec"
			tablehead="Fiber Dec"
			description="ICRS declination of the fiber pointing"
			verbLevel="15"/>

		<column original="ssa_snr"
			description="Median signal to noise ratio over all pixel, computed
				as flux/sqrt(variance)"/>
		<column name="objtype" type="text"
			ucd="meta.code.class"
			tablehead="Ob. type"
			description="Object type: star, k2star, fs, rvcandi, gal"
			verbLevel="15"/>

		<column name="magtype" type="text"
			ucd="meta.code"
			tablehead="Mag.Type"
			description="Designation of the bands of the magnitudes in mag_arr;
				this is not easy to parse by a machine, sorry."
			verbLevel="15"/>
		<column name="mag_arr" type="real[]"
			ucd="phot.mag"
			tablehead="Mags."
			description="An array of derived magnitudes.  See mag_types for
				the bands these magnitudes are intended for."
			verbLevel="15"/>
		<column name="fibertype" type="text"
			ucd="meta.code;instr"
			tablehead="Fiber"
			description="Fiber Type of target (one of Obj, Sky, F-std,
				Unused, PosErr, Dead)"
			verbLevel="25"/>
		<column name="target_comment" type="text"
			ucd="meta.note"
			tablehead="Comment"
			description="Various comments (e.g., an external target id)."
			verbLevel="25"/>
		<column name="offset_v" type="real"
			unit="arcsec" ucd="instr.offset"
			tablehead="Offset"
			description="Offset of the observation from the target coordinate
				(these are added for bright objects to prevent saturation)."
			verbLevel="25"/>

		<column name="ra" type="double precision"
			unit="deg" ucd="pos.eq.ra;meta.main"
			tablehead="Target RA"
			description="ICRS right ascension for this object from input catalogue"
			verbLevel="1"/>
		<column name="dec" type="double precision"
			unit="deg" ucd="pos.eq.dec;meta.main"
			tablehead="Target Dec"
			description="ICRS declination for this object from input catalogue"
			verbLevel="1"/>

	</STREAM>

	<table id="medres" onDisk="True" adql="hidden"
			namePath="//ssap#instance" primary="mobsid">
		<meta name="description">Metadata of LAMOST DR6 medium resolution
			spectra.  This table is only used to feed the ssa_mrs view;
			use that to query data in this table.</meta>

		<index columns="ssa_dateObs"/>
		<index columns="ssa_specstart"/>
		<index columns="ssa_specend"/>

		<column name="mobsid" type="text" required="True"
			ucd="meta.id;meta.main"
			description="Unique identifier for the medium resolution spectrum
				(most of these have multiple observations and include a coadded
				spectrum)."
			verbLevel="1"/>

		<FEED source="catcolumns"/>

		<column original="ssa_specstart"/>
		<column original="ssa_specend"/>
		<column original="ssa_specres"/>

		<LOOP>
			<csvItems>
				bandid, source
				b0,    the B spectrum after continuum removal
				r0,    the R spectrum after continuum removal
				br0,   the B and R spectra after continuum removal
				lasp0, the LAMOST stellar parameter pipeline
			</csvItems>
			<events>
				<column name="rv_\bandid"
					unit="km/s" ucd="spect.dopplerVeloc"
					tablehead="RV \bandid"
					description="Radial velocity estimated from \source."/>
				<column name="rv_\bandid\+_err"
					unit="km/s" ucd="stat.error;spect.dopplerVeloc"
					tablehead="Err. RV \bandid"
					description="Error in rv_\bandid."/>
			</events>
		</LOOP>

		<LOOP>
			<csvItems>
				bandid, source
				b1,    b0
				r1,    r0
				br1,   br0
				lasp1, lasp0
			</csvItems>
			<events>
				<column name="rv_\bandid"
					unit="km/s" ucd="spect.dopplerVeloc"
					tablehead="RV \bandid"
					description="rv_\source after zero-point correction"/>
				<column name="rv_\bandid\+_err"
					unit="km/s" ucd="stat.error;spect.dopplerVeloc"
					tablehead="Err. RV \bandid"
					description="Error in rv_\bandid."/>
			</events>
		</LOOP>

		<LOOP>
			<csvItems>
				bandid
				b
				r
				br
			</csvItems>
			<events>
				<column name="rv_\bandid\+_flag" type="smallint" required="True"
					ucd="meta.code.qual"
					tablehead="\bandid ?"
					description="Flag for RV extraction in the \bandid band:
						0 – no anomalies; 1 – too many bad pixels; 2 – radial velocity
						in excess of 450 km/s; 3 – low similarity with best-matched
						template."/>
			</events>
		</LOOP>

		<column name="coadd" type="smallint" required="True"
			ucd="meta.code"
			tablehead="Coadd?"
			description="1 if this is a co-added spectrum"
			verbLevel="25"/>
		<column name="fibermask" type="smallint" required="True"
			ucd="meta.code.qual"
			tablehead="Fiber prob"
			description="Bitmask for fiber problems.  See note for the meaning
				for the bits."
			verbLevel="25"
			note="fib"/>
		<column name="bad_b" type="smallint" required="True"
			ucd="meta.code"
			tablehead="B bad?"
			description="1 if this is a problematic B spectrum."
			verbLevel="15"/>
		<column name="bad_r" type="smallint" required="True"
			ucd="meta.code"
			tablehead="R bad?"
			description="1 if this is a problematic R spectrum."
			verbLevel="15"/>

		<meta name="note" tag="fib">
			Meaning of the bits in fibermask (note: while upstream counts bits
			from 1, we count bits from 0):

			=== ================================================
			Bit  Meaning
			=== ================================================
			0   Fiber not allotted
			1   Bad trace from the TRACECENTER routine
			2   Low counts in flat field
			3   Bad arc solution
			4   &lt;10% pixels are bad on the CCD
			5   &lt;10% pixels are saturated
			6   Whopping fiber
			7   Near a whopping fiber
			8   Sky fiber shows extreme residuals
			=== ================================================
		</meta>
	</table>

	<table id="lowres" onDisk="True" adql="hidden"
			namePath="//ssap#instance" primary="obsid">
		<meta name="description">Metadata of LAMOST DR6 low resolution
			spectra.  This table is only used to feed the ssa_lrs view;
			use that to query data in this table.</meta>

		<index columns="ssa_dateObs"/>

		<FEED source="catcolumns"/>
		<column original="ssa_redshift"
			description="Redshift as estimated by the LAMOST pipeline."
			verbLevel="15"/>
		<column name="z_err"
			ucd="stat.error;src.redshift"
			tablehead="σ_z"
			description="Error in ssa_redshift as estimated by the LAMOST pipeline."
			verbLevel="25"/>
	</table>

	<coverage>
		<updater spaceTable="lowres" timeTable="lowres"/>
		<!-- MRS coverage; LRS is wider -->
		<spectral>3.71e-19 4.01e-19</spectral>
	</coverage>

	<NXSTREAM id="import_recipe">
		<doc>Common build material for lo-res and med-res data items.
		
			Config:

			* target_table -- id of the table to build
			* id_fragment -- lrs or mrs
			* id_source -- mobsid for mrs, obsid for lrs

			The rowmakers include a stream lrs-extramaps or mrs-extramaps
			with mapping special to the low and med res tables.
		</doc>
		<recreateAfter>make_\target_table\+_view</recreateAfter>
		<fitsTableGrammar>
			<rowfilter procDef="//products#define">
				<bind key="table">"\\schema.\target_table"</bind>
				<bind key="accref">"lamost6/\id_fragment/"+str(@\id_source)</bind>
				<bind key="path"
					>("\\internallink{\\rdId/sdl_\id_fragment/dlget}?ID=lamost6/"
						+"\id_fragment/"+str(@\id_source))</bind>
				<bind key="fsize">100000</bind>
				<bind key="datalink">"\\rdId#sdl_\id_fragment"</bind>
				<bind key="mime">"application/x-votable+xml"</bind>
				<bind key="preview">makeAbsoluteURL("\\rdId/preview/qp/"
					"\id_fragment/"+str(@obsid)+"/"+str(@\id_source))</bind>
				<bind key="preview_mime">"image/png"</bind>
			</rowfilter>
		</fitsTableGrammar>

		<make table="\target_table">
			<rowmaker idmaps="*">
				<var name="obsdate">dateTimeToMJD(parseDate(@obsdate))</var>
				
				<apply>
					<setup>
						<code>
							ok_terms = {"star", "k2star", "fs", "rvcandi", "gal"}
						</code>
					</setup>
					<code>
						key = @objtype.lower()
						if key in ok_terms:
							@objtype = key
						else:
							@objtype = "unknown (%s)"%key
					</code>
				</apply>

				<apply procDef="//ssap#fill-plainlocation">
					<bind key="ra">@ra</bind>
					<bind key="dec">@dec</bind>
					<bind key="aperture">1/3600.</bind>
				</apply>

				<map key="target_comment">"(from %s) %s"%(@tfrom, @tcomment)</map>
				<map key="offset_v">@offsets_v if @offsets else None</map>
				<map key="ssa_dateObs">@obsdate</map>
				<map key="mag_arr">[(NaN if v==99. else v)
					for v in [@mag1, @mag2, @mag3, @mag4, @mag5, @mag6, @mag7]]
				</map>

				<FEED source="\id_fragment-extramaps"/>
			</rowmaker>
		</make>
	</NXSTREAM>

	<STREAM id="lrs-extramaps">
		<map key="ssa_redshift" nullExpr="-9999." source="z"/>
		<map key="z_err" nullExpr="-9999."/>

		<apply name="computeMeanSNR">
			<code>
				vals = [v for v in [@snru, @snrg, @snrr, @snri, @snrz] if v!=-9999.]
				if vals:
					@ssa_snr = sum(vals)/len(vals)
				else:
					@ssa_snr = None
			</code>
		</apply>
	</STREAM>

	<STREAM id="mrs-extramaps">
		<map key="ssa_snr" source="snr" nullExpr="-9999."/>
		<LOOP listItems="rv_b0 rv_b0_err rv_r0 rv_r0_err rv_br0 rv_br0_err
			rv_lasp0 rv_lasp0_err rv_b1 rv_b1_err rv_r1 rv_r1_err rv_br1
			rv_br1_err rv_lasp1 rv_lasp1_err rv_b_flag rv_r_flag rv_br_flag">
			<events>
				<map key="\item" source="\item" nullExpr="-9999."/>
			</events>
		</LOOP>
		<apply name="setLimits">
			<code>
				if @prodtblAccref.endswith("R"):
					@ssa_specstart, @ssa_specend = 6.28e-7, 6.87e-7
					@ssa_specres = 0.15e-10
				else:
					@ssa_specstart, @ssa_specend = 4.91e-7, 5.37e-7
					@ssa_specres = 0.11e-10
			</code>
		</apply>
		<!-- I was tempted to derive a redshift from the radial velocity,
			but since the maximal RV they got was about 600 km/s, that
			wouldn't make much sense -->
	</STREAM>

	<data id="import_mrs">
		<sources recurse="True"
			pattern="data/MRS-catalogue.fits.gz"/>
		<FEED source="import_recipe"
			target_table="medres"
			id_fragment="mrs"
			id_source="mobsid"/>
	</data>

	<data id="import_lrs">
		<sources recurse="True"
			pattern="data/LRS-catalogue.fits.gz"/>
		<FEED source="import_recipe"
			target_table="lowres"
			id_fragment="lrs"
			id_source="obsid"/>
	</data>

	<STREAM id="view_def">
		<doc>The material for both the low- and medres SSA views.

		Config:
		* sourcetable -- medres or lowres
		* collection_name -- LAMOST6 MRS or LRS
		* datalinksvc -- id of the associated datalink service
		* ssa_specstart/ssa_specend -- literals for lrs, colrefs for mrs
		* ssa_length
		</doc>
		<meta name="_associatedDatalinkService">
			<meta name="serviceId">\datalinksvc</meta>
			<meta name="idColumn">ssa_pubDID</meta>
		</meta>

		<mixin
			sourcetable="\sourcetable"
			copiedcolumns="*"
			ssa_aperture="1/3600."
			ssa_dstitle="'\collection_name ' || obsid"
			ssa_cversion="'DR 6'"
			ssa_fluxunit="'count'"
			ssa_spectralunit="'Angstrom'"
			ssa_bandpass="'Optical'"
			ssa_collection="'\collection_name'"
			ssa_fluxcalib="'RELATIVE'"
			ssa_fluxucd="'phot.flux.density;em.wl'"
			ssa_speccalib="'ABSOLUTE'"
			ssa_spectralucd="'em.wl'"
			ssa_targname="designation"
			ssa_targclass="objtype"
			ssa_creator="'NAOC-LAMOST'"
			ssa_datasource="'survey'"
			ssa_specstart="\ssa_specstart"
			ssa_specend="\ssa_specend"
			ssa_specmid="(\ssa_specend+\ssa_specstart)/2"
			ssa_specext="\ssa_specend-\ssa_specstart"
			ssa_length="\ssa_length"
			ssa_binSize="\ssa_specres"
			ssa_specres="\ssa_specres"
			ssa_timeExt="3600"
		>//ssap#view</mixin>

		<mixin
			calibLevel="2"
			coverage="ssa_region"
			sResolution="ssa_spaceRes"
			oUCD="ssa_fluxucd"
			emUCD="ssa_spectralucd"
			t_xel="1"
			t_min="ssa_dateObs"
			t_max="ssa_dateObs"
			>//obscore#publishSSAPMIXC</mixin>
	</STREAM>

	<table id="ssa_mrs" onDisk="True" adql="True" nrows="5900000">
		<meta name="description">SSA-compliant metadata for LAMOST DR6
			medium resolution spectra; this data is also availble through
			Obscore; the collection name there is LAMOST6 MRS.</meta>
		<FEED source="view_def"
			sourcetable="medres"
			collection_name="LAMOST6 MRS"
			datalinksvc="sdl_mrs"
			ssa_specres="ssa_specres"
			ssa_specstart="ssa_specstart"
			ssa_specend="ssa_specend"
			ssa_length="3800"/>
	</table>

	<table id="ssa_lrs" onDisk="True" adql="True" nrows="10000000">
		<meta name="description">SSA-compliant metadata for LAMOST DR6
			low resolution spectra; this data is also availble through
			Obscore; the collection name there is LAMOST6 LRS.</meta>
		<FEED source="view_def"
			sourcetable="lowres"
			collection_name="LAMOST6 LRS"
			datalinksvc="sdl_lrs"
			ssa_specres="1e-10"
			ssa_specstart="3.7e-7"
			ssa_specend="9.1e-7"
			ssa_length="3900"/>
	</table>

	<data id="make_medres_view" auto="False">
		<make table="ssa_mrs"/>
	</data>

	<data id="make_lowres_view" auto="False">
		<make table="ssa_lrs"/>
	</data>

	<table id="instance_mrs" onDisk="False">
		<mixin ssaTable="ssa_mrs"
			spectralDescription="Wavelength"
			fluxDescription="Flux"
			>//ssap#sdm-instance</mixin>
		<meta name="description">A medium-resolution LAMOST spectrum</meta>
		<column name="flux_error"
			ucd="stat.error;phot.flux.density;em.wl"
			utype="spec:data.fluxaxis.accuracy.staterror"
			tablehead="Err(flux)"
			description="Flux error, comuted as sqrt(invsig),
				where invsig is the inverse simga^2 from the LAMOST FITS."/>
		<column name="flags" type="smallint"
			ucd="meta.code.qual"
			tablehead="Flags"
			description="Extraction flags; this is the 'pixmask' from the upstream
				data, where a bit is set if a condition is met on one or more of the
				exposures: Bit 0: Bad pixel on CCD; bit 1: Bad profile in extraction;
				bit 2: No sky information at this wavelength; bit 3: Sky level to
				high; bit 4: fiber trace out of the CCD; bit 5: no good data.
				
				In the case of coadded spectra, we report the 'ormask', i.e.,
				we will report a pixel as bad when any of the contributing
				spectra were bad in that position.">
			<values nullLiteral="-1"/>
		</column>
	</table>

	<table id="instance_lrs" onDisk="False">
		<mixin ssaTable="ssa_lrs"
			spectralDescription="Wavelength"
			fluxDescription="Flux"
			>//ssap#sdm-instance</mixin>
		<meta name="description">A low-resolution LAMOST spectrum</meta>
		<column name="flux_error"
			ucd="stat.error;phot.flux.density;em.wl"
			tablehead="Err(flux)"
			description="Flux error, comuted as sqrt(invsig),
				where invsig is the inverse simga^2 from the LAMOST FITS."/>
		<column name="flags" type="smallint"
			ucd="meta.code.qual"
			tablehead="Flags"
			description="Extraction flags; this is the 'pixmask' from the upstream
				data, where a bit is set if a condition is met on one or more of the
				exposures: Bit 0: Bad pixel on CCD; bit 1: Bad profile in extraction;
				bit 2: No sky information at this wavelength; bit 3: Sky level to
				high; bit 4: fiber trace out of the CCD; bit 5: no good data.
				
				We report the 'ormask', i.e., we will report a pixel as bad
				when any of the contributing spectra were bad in that position.">
			<values nullLiteral="-1"/>
		</column>
	</table>

<!-- MRS datalink and SDM spectrum making service -->
	<data id="build_mrs_spectrum" auto="False">
		<embeddedGrammar>
			<iterator>
				<setup imports="gavo.utils.pyfits"/>
				<code>
					# this expects both mobsid and obsid in sourceToken and
					# will locate the file and extension based on that.
					obsid = str(self.sourceToken["obsid"])
					sourcePath = rd.getAbsPath("data/MRS/%s/%s.fits"%(
						obsid[5:], obsid))
					maskField = "PIXMASK"

					specid = self.sourceToken["mobsid"][len(obsid):]
					if len(specid)==1:
						specid = "COADD_"+specid
						maskField = "ORMASK"
					else:
						specid = "%s-%s"%(specid[-1], specid[:-1])

					with pyfits.open(sourcePath, memmap=True) as hdus:
						for row in hdus[specid].data:
							yield {
							"spectral": 10**row["LOGLAM"],
							"flux": row["FLUX"],
							"flux_error": 1/math.sqrt(row["IVAR"]) if row["IVAR"] else None,
							"flags": row[maskField],
							}
				</code>
			</iterator>
		</embeddedGrammar>
		<make table="instance_mrs">
			<parmaker>
				<apply procDef="//ssap#feedSSAToSDM"/>
			</parmaker>
		</make>
	</data>

	<service id="sdl_mrs" allowed="dlget,dlmeta,static">
		<meta name="title">\schema MRS Datalink Service</meta>
		<meta name="description">Spectrum datalink service for LAMOST6
			medium-resolution spectra.  This gives you access to single-exposure
			spectra and the original FITS files.</meta>
		<property key="staticData">data/MRS</property>

		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind key="ssaTD">"\rdId#ssa_mrs"</bind>
			</descriptorGenerator>
			<dataFunction procDef="//soda#sdm_genData">
				<bind key="builder">"\rdId#build_mrs_spectrum"</bind>
			</dataFunction>
			<FEED source="//soda#sdm_plainfluxcalib"/>
			<FEED source="//soda#sdm_cutout"/>
			<FEED source="//soda#sdm_format"/>

			<metaMaker name="addSingleSpectrumLinks" semantics="#progenitor">
				<code>
					if descriptor.ssaRow.get("coadd")==1:
						# add rows for the spectra that went into this coadd
						with base.getTableConn() as conn:
							for row in conn.queryToDicts("SELECT mobsid, accref"
									" FROM lamost6.ssa_mrs"
									" WHERE obsid=%(obsid)s"
									" AND coadd=0", {"obsid": descriptor.ssaRow["obsid"]}):

								yield descriptor.makeLink(
									makeAbsoluteURL("\rdId/sdl_mrs/dlmeta?ID="+row["accref"]),
									description="A single-exposure LAMOST spectrum"
										" of this source.",
									contentType="application/x-votable+xml;content=datalink",
									contentQualifier="#spectrum")
				</code>
			</metaMaker>
			
			<metaMaker name="addCoaddLink" semantics="#derivation">
				<code>
					if descriptor.ssaRow.get("coadd")==0:
						# add a rows for the coadd if this a a single-exposure
						with base.getTableConn() as conn:
							for row in conn.queryToDicts("SELECT mobsid, accref"
									" FROM lamost6.medres"
									" WHERE obsid=%(obsid)s"
									" AND coadd=1", {"obsid": descriptor.ssaRow["obsid"]}):
								
								if row["accref"][-1]==descriptor.pubDID[-1]:
									# same band, coadd is a derivation
									yield descriptor.makeLink(
										makeAbsoluteURL("\rdId/sdl_mrs/dlmeta?ID="+row["accref"]),
										description="The co-added spectrum this went into",
										contentType="application/x-votable+xml;content=datalink",
										contentQualifier="#spectrum")

								else:
									# same band, coadd is a counterpart
									yield descriptor.makeLink(
										makeAbsoluteURL("\rdId/sdl_mrs/dlmeta?ID="+row["accref"]),
										semantics="#counterpart",
										description="A co-added spectrum of this object"
											" in the %s band."%row["accref"][-1],
										contentType="application/x-votable+xml;content=datalink",
										contentQualifier="#spectrum")
				</code>
			</metaMaker>

			<metaMaker name="addFITSLink" semantics="#progenitor">
				<code>
					obsid = str(descriptor.ssaRow["obsid"])
					yield descriptor.makeLink(
						makeAbsoluteURL("\rdId/sdl_mrs/static/%s/%s.fits"%(
							obsid[5:], obsid)),
							description="Original, multi-extension, dataset in FITS"
								" binary table",
							contentType="application/fits",
							contentQualifier="#spectrum")
				</code>
			</metaMaker>
		</datalinkCore>
	</service>

	<service id="preview" allowed="qp">
		<meta name="title">LAMOST6 spectra preview maker</meta>
		<property name="queryField">specid</property>
		<pythonCore>
			<inputTable>
				<inputKey name="specid" type="text" required="True"
					description="ID of the spectrum to produce a preview for"/>
			</inputTable>
			<coreProc>
				<setup imports="gavo.helpers.processing.SpectralPreviewMaker,
					gavo.utils.pyfits"/>
				<code>
					# This is complicated because there are several
					# spectra per MRS file; that's why the products#define
					# apply adds two segments: for LRS, that's both obsid,
					# but for MRS that's obsid and mobsid.  These we
					# feed to the grammar of build_mrs_spectrum as appropriate.
					obsid, mobsid = inputTable.args["specid"].split("/")[-2:]
					if obsid==mobsid:
						# LRS
						sourcePath = rd.getAbsPath("data/LRS/%s/%s.fits"%(
							obsid[5:], obsid))
						with pyfits.open(sourcePath) as hdus:
							arr = hdus[0].data
							res = list(zip(arr[2], arr[0]))
					else:
						# MRS
						sourceToken = {"obsid": obsid, "mobsid": mobsid}
						res = [(r["spectral"], r["flux"])
							for r in rd.getById("build_mrs_spectrum"
								).grammar.parse(sourceToken)]

					return ("image/png",
						SpectralPreviewMaker.get2DPlot(res, linear=True))
				</code>
			</coreProc>
		</pythonCore>
	</service>


<!-- LRS datalink and SDM spectrum making service -->

	<data id="build_lrs_spectrum" auto="False">
		<embeddedGrammar>
			<iterator>
				<setup imports="gavo.utils.pyfits,gavo.utils.fitstools"/>
				<code>
					# This wants obsid in sourceToken and will build the
					# source file name from that.
					obsid = str(self.sourceToken["obsid"])
					sourcePath = rd.getAbsPath("data/LRS/%s/%s.fits"%(					
						obsid[5:], obsid))

					with pyfits.open(sourcePath, memmap=True) as hdus:
						# LAMOST WCS is severely broken: CD1_1 for a spectral axis.  Oh my.
						# Let's fiddle things by hand, and then do log/10** manually below.
						hdr, data = hdus[0].header, hdus[0].data
						specWCS = fitstools.WCSAxis(
							"spectral",
							hdr["CRVAL1"],
							hdr["CRPIX1"],
							hdr["CD1_1"],
							axisLength=hdr["NAXIS1"])
						
						for pixInd, row in enumerate(data.T):
							yield {
								"spectral": row[2],
								"flux": row[0],
								"flux_error": 1/math.sqrt(row[1]) if row[1] else None,
								"flags": int(row[4])
							}
				</code>
			</iterator>
		</embeddedGrammar>
		<make table="instance_lrs">
			<parmaker>
				<apply procDef="//ssap#feedSSAToSDM"/>
			</parmaker>
		</make>
	</data>

	<service id="sdl_lrs" allowed="dlget,dlmeta,static">
		<meta name="title">\schema LRS Datalink Service</meta>
		<meta name="description">Spectrum datalink service for LAMOST6
			low-resolution spectra.  This gives you access to the SDM-compliant
			data in various formats as well as the original FITS files.</meta>
		<property key="staticData">data/LRS</property>

		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind key="ssaTD">"\rdId#ssa_lrs"</bind>
			</descriptorGenerator>
			<dataFunction procDef="//soda#sdm_genData">
				<bind key="builder">"\rdId#build_lrs_spectrum"</bind>
			</dataFunction>
			<FEED source="//soda#sdm_plainfluxcalib"/>
			<FEED source="//soda#sdm_cutout"/>
			<FEED source="//soda#sdm_format"/>

			<metaMaker name="addFITSLink" semantics="#progenitor">
				<code>
					obsid = str(descriptor.ssaRow["obsid"])
					yield descriptor.makeLink(
						makeAbsoluteURL("\rdId/sdl_lrs/static/%s/%s.fits"%(
							obsid[5:], obsid)),
							description="Original dataset as a FITS array",
							contentType="application/fits",
							contentQualifier="#spectrum")
				</code>
			</metaMaker>
		</datalinkCore>
	</service>


	<STREAM id="web-output-table">
		<outputTable>
			<autoCols>accref, mime, ssa_targname,
				ssa_aperture, ssa_dateObs</autoCols>
			<FEED source="//ssap#atomicCoords"/>
			<outputField original="ssa_specstart" displayHint="displayUnit=Angstrom"/>
			<outputField original="ssa_specend" displayHint="displayUnit=Angstrom"/>
			<outputField name="datalink" type="text"
				ucd="meta.ref.url"
				select="'\\getConfig{web}{serverURL}/\\rdId/sdl_mrs/dlmeta?ID='
      		|| gavo_urlescape(ssa_pubDID)" tablehead="DL"
      		description="URL of a datalink document for this dataset."
      		displayHint="type=url" verbLevel="1"/>
		</outputTable>
	</STREAM>


	<service id="mrs_web" defaultRenderer="form">
		<meta name="shortName">LM6 MRS Web</meta>
		<meta name="title">LAMOST DR6 Medium Resolution Spectra Web</meta>
		<meta name="description">A form-based interface to the LAMOST6
			medium resolution spectra</meta>

		<dbCore queriedTable="ssa_mrs">
			<condDesc buildFrom="ssa_location"/>
			<condDesc buildFrom="ssa_dateObs"/>
			<condDesc combining="True" id="select_only_coadded">
				<phraseMaker>
					<code>
						yield "coadd=1"
					</code>
				</phraseMaker>
			</condDesc>
		</dbCore>

		<FEED source="web-output-table"/>
	</service>

	<service id="svc_mrs" allowed="form,ssap.xml">
		<meta name="shortName">LM6 MRS SSAP</meta>
		<meta name="ssap.complianceLevel">full</meta>
		<meta name="title">LAMOST DR6 Medium Resolution Spectra</meta>
		<meta name="description">A machine-readable interface to the LAMOST6
			medium resolution spectra based on SSAP and datalink.</meta>

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

		<ssapCore queriedTable="ssa_mrs">
			<property key="previews">auto</property>
			<FEED source="//ssap#hcd_condDescs"/>
			<condDesc original="select_only_coadded"/>
		</ssapCore>
	</service>

	<service id="lrs_web" defaultRenderer="form">
		<meta name="shortName">LM6 LRS Web</meta>
		<meta name="title">LAMOST DR6 Low Resolution Spectra Web</meta>
		<meta name="description">A form-based interface to the LAMOST6
			low resolution spectra</meta>

		<dbCore queriedTable="ssa_lrs">
			<condDesc buildFrom="ssa_location"/>
			<condDesc buildFrom="ssa_dateObs"/>
		</dbCore>

		<FEED source="web-output-table"/>
	</service>

	<service id="svc_lrs" allowed="form,ssap.xml">
		<meta name="shortName">LM6 LRS SSAP</meta>
		<meta name="ssap.complianceLevel">full</meta>
		<meta name="title">LAMOST DR6 Low Resolution Spectra</meta>
		<meta name="description">A machine-readable interface to the LAMOST6
			low resolution spectra based on SSAP and datalink.</meta>

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

		<ssapCore queriedTable="ssa_lrs">
			<property key="previews">auto</property>
			<FEED source="//ssap#hcd_condDescs"/>
		</ssapCore>
	</service>

	<regSuite title="lamost6 MRS regression">
		<regTest title="lamost6 MRS coadd datalink.">
			<url ID="lamost6/mrs/588902003B"
				>sdl_mrs/dlmeta</url>
			<code>
				rows = self.getVOTableRows()
				self.assertEqual(len(rows), 22)
				triples = set((r["semantics"], r["content_type"],
						r["access_url"] and r["access_url"][-30:])
					for r in rows)
				self.assertTrue(('#progenitor',
					'application/x-votable+xml;content=datalink',
					'lamost6/mrs/58890200383556105R') in triples,
					"single-exposure link missing")
				self.assertTrue(
					('#preview', 'image/png', 'ew/qp/mrs/588902003/588902003B')
						in triples,
					"No preview?")
				# The link in the next test is tested for existence below.
				self.assertTrue(
					('#progenitor', 'application/fits',
						'mrs/static/2003/588902003.fits') in triples,
					"No FITS source?")

				for r in rows:
					if r["semantics"] in ("#this", "#progenitor", "#counterpart"):
						self.assertEqual(r["content_qualifier"], "#spectrum")
			</code>
		</regTest>

		<regTest title="lamost6 MRS single exposure datalink.">
			<url ID="lamost6/mrs/58890200383556105R"
				>sdl_mrs/dlmeta</url>
			<code>
				rows = self.getVOTableRows()
				self.assertEqual(len(rows), 6)
				triples = set((r["semantics"], r["content_type"],
						r["access_url"] and r["access_url"][-30:])
					for r in rows)
				self.assertTrue(('#derivation',
					'application/x-votable+xml;content=datalink',
					'meta?ID=lamost6/mrs/588902003R') in triples,
					"link to co-add missing")
				self.assertTrue(('#counterpart',
					'application/x-votable+xml;content=datalink',
					'meta?ID=lamost6/mrs/588902003B') in triples,
					"link to alt-band co-add missing")
				# The link in the next text is tested for existence below.
				self.assertTrue(
					('#progenitor', 'application/fits',
						'mrs/static/2003/588902003.fits') in triples,
					"No FITS source?")
			</code>
		</regTest>

		<regTest title="lamost6 Datalink delivers single spectrum in fits.">
			<url ID="ivo://org.gavo.dc/~?lamost6/mrs/58890200583556119B"
				FORMAT="application/fits"
				>sdl_mrs/dlget</url>
			<code>
				self.assertHasStrings(
					"NAXIS2  =                 4136",
					"TUTYP1  = 'spec:spectrum.data.spectralaxis.value'")
			</code>
		</regTest>

		<regTest title="lamost6 Datalink delivers coadd data in votable.">
			<url ID="lamost6/mrs/588902003B"
				>sdl_mrs/dlget</url>
			<code>
				r0 = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(r0["flux"], 0)
				self.assertAlmostEqual(r0["flux_error"], 13.357676506042)
				self.assertAlmostEqual(r0["spectral"], 4916.0905436843, places=2)
			</code>
		</regTest>

		<regTest title="LAMOST6 MRS SSA works.">
			<url POS="15.3672776,4.0094024" SIZE="0.00001" REQUEST="queryData"
				TIME="/2017-10-04"
				>svc_mrs/ssap.xml</url>
			<code>
				res = self.getVOTableRows()
				self.assertEqual(len(res), 2)
				for r in res:
					self.assertEqual(res[0]["ssa_pubDID"][:-1],
						"ivo://org.gavo.dc/~?lamost6/mrs/588902003")
					if res[0]["ssa_pubDID"].endswith("B"):
						break
				else:
					raise AssertionError("No blue spectrum?")
				self.assertEqual(res[0]["ssa_dateObs"], 58024.0)
			</code>
		</regTest>

		<regTest title="MRS single exposure preview looks like a PNG">
			<url>preview/qp/588902003/58890200383556078B</url>
			<code>
				self.assertHasStrings("PNG", b"IDATx\\x9c\\xed")
			</code>
		</regTest>

		<regTest title="MRS coadd preview looks like a PNG">
			<url>preview/qp/588902003/588902003B</url>
			<code>
				self.assertHasStrings("PNG", "BBF", "BBD")
			</code>
		</regTest>

		<regTest title="MRS source FITS delivered">
			<url>sdl_mrs/static/2003/588902003.fits</url>
			<code>
				self.assertHasStrings("SIMPLE", "LAMOST DR6 INTER", "PCOUNT  =")
			</code>
		</regTest>
	</regSuite>

	<regSuite title="lamost6 LRS regression">
		<regTest title="lamost6 LRS datalink">
			<url ID="lamost6/lrs/101026"
				>sdl_lrs/dlmeta</url>
			<code>
				rows = self.getVOTableRows()
				self.assertEqual(len(rows), 4)
				triples = set((r["semantics"], r["content_type"],
						r["access_url"] and r["access_url"][-30:])
					for r in rows)
				
				# the individual links are tested below
				self.assertTrue(
					('#progenitor', 'application/fits', 'q/sdl_lrs/static/6/101026.fits')
					in triples, "No source fits?")
				self.assertTrue(
				 ('#preview', 'image/png', 'q/preview/qp/lrs/101026/101026')
				 in triples, "No preview?")
				self.assertTrue(
					('#this', 'application/x-votable+xml',
						'/getproduct/lamost6/lrs/101026')
					in triples, "No this?")
			</code>
		</regTest>

		<regTest title="LAMOST 6 LRS SDL formatting">
			<url httpHonorRedirects="True">/getproduct/lamost6/lrs/101026</url>
			<code>
				self.assertHasStrings("DESCRIPTION>Flux error, comuted as sqrt(invs",
					'value="http://dr6.lamost.org/v2/"')
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(row["spectral"], 3699.986328125)
				self.assertAlmostEqual(row["flux"], 235.5823516845703)
				self.assertAlmostEqual(row["flux_error"], 134.980453491211)
			</code>
		</regTest>

		<regTest title="LAMOST 6 LRS original FITS">
			<url>sdl_lrs/static/6/101026.fits</url>
			<code>
				self.assertHasStrings("SIMPLE  =",
					"MAG1    =                17.58")
			</code>
		</regTest>

		<regTest title="LAMOST 6 LRS preview">
			<url>preview/qp/lrs/101026/101026</url>
			<code>
				self.assertHasStrings("PNG", "q;MGW")
			</code>
		</regTest>

		<regTest title="LAMOST 6 LRS SSA">
			<url POS="331.551234,-1.684256" SIZE="0.001" REQUEST="queryData"
				>svc_lrs/ssap.xml</url>
			<code>
				res = self.getVOTableRows()
				self.assertEqual(len(res), 1)
				self.assertEqual(res[0]["designation"], "J220612.29-014103.6")
			</code>
		</regTest>
	</regSuite>
</resource>
