<resource schema="openngc">
	<meta name="title">OpenNGC</meta>
	<meta name="description">
		OpenNGC is a database containing positions and main data of NGC (New
		General Catalogue) and IC (Index Catalogue) objects.  It has been built by
		merging data from NED, HyperLEDA, SIMBAD, and several databases available
		at HEASARC.

		In this VO publication, we have changed most of the column names, mostly
		to make them work as ADQL column names without resorting to delimited
		identifiers.  The mapping should be obvious.
	</meta>
	<meta name="creationDate">2017-07-18T14:00:00</meta>
	<meta name="schema-rank">50</meta>

	<meta name="subject">nebulae</meta>
	<meta name="subject">surveys</meta>

	<meta name="creator">Verga, M.</meta>

	<meta name="source">https://github.com/mattiaverga/OpenNGC</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Catalog</meta>

	<meta name="_news" author="MD" date="2023-12-13">Updated to
		upstream commit 1739 7545, which added a few objects
		and fixed some minor mistakes.</meta>

	<meta name="_news" author="MD" date="2022-06-13">Updated to
		upstream commit  4ffc 6be0, which adds proper motions and
		radial velocities/redshifts to many objects.</meta>

	<meta name="_news" author="MD" date="2021-12-29">Updated to
		upstream commit  da4b 5cb1, which improves the presentation
		of the sources of the data.</meta>

	<meta name="_news" author="MD" date="2021-11-15">Updated to
		upstream commit 3ae3ee5, containing numerous fixes after
		a comparison with Harold Corwin's notes.</meta>
	
	<meta name="_news" author="MD" date="2020-05-19">Updated to
		v20200329; also adding the first outlines as MOCs</meta>

	<meta name="_news" author="MD" date="2019-10-21">Updated to
		v20191019</meta>

	<meta name="_news" author="MD" date="2019-09-23">Updated to
		v20190920</meta>

	<meta name="_news" author="MD" date="2019-06-11">Updated to
		v20190609</meta>

	<meta name="doi">10.21938/y.1ejWUD_MQ6b_eDFoVbbw</meta>

	<FEED source="//procs#license-cc-by-sa" what="OpenNGC"/>

	<table id="data" onDisk="True" adql="True">
		<meta name="description">The OpenNGC object list with the main
			metadata</meta>
		<publish/>
		<index columns="name"/>
		<index columns="obj_type"/>
		<index columns="hubble_type"/>

		<stc>
			Position ICRS Epoch J2000 "raj2000" "dej2000"
		</stc>

		<column name="name" type="text"
			ucd="meta.id;meta.main"
			tablehead="Name"
			description="Object identifier, either from NGC or from IC."
			verbLevel="1"/>
		<column name="obj_type" type="text"
			ucd="src.class"
			tablehead="Type"
			description="Object Type.  See note for terms and symbols used."
			verbLevel="1" note="t">
		</column>
		<column name="raj2000" type="double precision"
			unit="deg" ucd="pos.eq.ra;meta.main"
			tablehead="RA J2000"
			description="Right Ascension in ICRS for Epoch J2000.0."
			displayHint="type=hms,sf=0"
			verbLevel="1"/>
		<column name="dej2000" type="double precision"
			unit="deg" ucd="pos.eq.dec;meta.main"
			tablehead="Dec J2000"
			description="Declination in ICRS for Epoch J2000.0."
			displayHint="type=dms,sf=0"
			verbLevel="1"/>
		<column name="constellation" type="text"
			ucd="meta.id.parent"
			tablehead="Const."
			description="Constellation the object is located in."
			verbLevel="15"/>

		<column name="maj_ax_deg"
			unit="deg" ucd="phys.angSize.sMajAxis"
			tablehead="Maj. Ax."
			description="Major axis of covering ellipsoid."
			displayHint="displayUnit=arcmin,sf=2" verbLevel="15"/>
		<column name="min_ax_deg"
			unit="deg" ucd="phys.angSize.sMinAxis"
			tablehead="Min. Ax."
			description="Minor axis of covering ellipsoid."
			displayHint="displayUnit=arcmin,sf=2" verbLevel="15"/>
		<column name="pos_ang"
			unit="deg" ucd="pos.posAng"
			tablehead="Pos. Ang."
			description="Position angle of covering ellipsoid, north over east."
			displayHint="sf=0" verbLevel="15"/>

		<LOOP>
			<csvItems>
				bandid, bandname, ucd
				B,     B Band, em.opt.B
				V,     V Band, em.opt.V
				J,     J Band, em.ir.J
				H,     H Band, em.ir.H
				K,     K Band, em.ir.K
			</csvItems>
			<events>
				<column name="mag_\decapitalize{\bandid}"
					unit="mag" ucd="phot.mag;\ucd"
					tablehead="m_\bandid"
					description="Apparent total magnitude in the \bandname."
					verbLevel="15"/>
			</events>
		</LOOP>

		<column name="surf_br_B"
			unit="mag/arcsec**2" ucd="phot.mag.sb"
			tablehead="Surf. Br."
			description="Mean surface brigthness within the 25 mag isophote (B-band);
				only given for galaxies"
			verbLevel="15"/>
		<column name="hubble_type" type="text"
			ucd="src.morph.type"
			tablehead="Hubble"
			description="Morphological Hubble type for galaxies."
			verbLevel="15">
			<values fromdb="hubble_type from \schema.data
				where hubble_type is not null
				order by hubble_type"/>
		</column>

		<column name="pmra"
			unit="mas/yr" ucd="pos.pm;pos.eq.ra"
			tablehead="PM RA"
			description="Proper motion in RA"
			verbLevel="15"/>
		<column name="pmdec"
			unit="mas/yr" ucd="pos.pm;pos.eq.dec"
			tablehead="PM Dec"
			description="Proper motion in Declination"
			verbLevel="15"/>
		<column name="rv"
			unit="km/s" ucd="spect.dopplerVeloc"
			tablehead="RV"
			description="Heliocentric radial velocity"
			verbLevel="15"/>
		<column name="z"
			ucd="src.redshift"
			tablehead="z"
			description="Heliocentric redshift"
			displayHint="sf=2" verbLevel="15"/>

		<LOOP>
			<csvItems>
				bandid, bandname, ucd
				U,     U Band, em.opt.U
				B,     B Band, em.opt.B
				V,     V Band, em.opt.V
			</csvItems>
			<events>
				<column name="cstar_mag_\decapitalize{\bandid}"
					unit="mag" ucd="phot.mag;\ucd"
					tablehead="* m_\bandid"
					description="For planetary nebulae: Apparent magnitude of
						the central star in the \bandname."
					verbLevel="15"/>
			</events>
		</LOOP>

		<column name="messier_nr" type="integer"
			ucd="meta.id.cross"
			tablehead="Messier"
			description="Messier number for this object"
			verbLevel="15">
			<values nullLiteral="-1"/>
		</column>
		<column name="other_ngc" type="text"
			ucd="meta.id.cross"
			tablehead="Alt. NGC"
			description="Other NGC number for this object in case it was duplicated
				in IC or NCG."
			verbLevel="15"/>
		<column name="ic_cross" type="text"
			ucd="meta.id.cross"
			tablehead="IC"
			description="Other IC number for this object in case it was duplicated
				in IC or NCG."
			verbLevel="15"/>
		<column name="cstar_id" type="text"
			ucd="meta.id.cross"
			tablehead="C* name"
			description="Identifier of the central star for planetary nebulae."
			verbLevel="15"/>
		<column name="other_id" type="text"
			ucd="meta.id.cross"
			tablehead="ID"
			description="Cross reference to other catalogs."
			verbLevel="15"/>
		<column name="comname" type="text"
			ucd=""
			tablehead="Common"
			description="Common name(s) of the object."
			verbLevel="15"/>
		<column name="ned_notes" type="text"
			ucd="meta.note"
			tablehead="NED note"
			description="Notes about the object exported from NED"
			verbLevel="15"/>
		<column name="sources" type="text"
			ucd="meta.bib"
			tablehead="Sources"
			description="Sources of the various categories of data"
			verbLevel="15"
			note="s"/>
		<column name="notes" type="text"
			ucd="meta.note"
			tablehead="Notes"
			description="Notes about the object from OpenNGC"
			verbLevel="15"/>

		<meta name="note" tag="t">
			The object type is encoded as follows:

			=========== ========================================
			``*``       Star
			``**``      Double star
			``*Ass``    Association of stars
			``OCl``     Open Cluster
			``GCl``     Globular Cluster
			``Cl+N``    Star cluster + Nebula
			``G``       Galaxy
			``GPair``   Galaxy Pair
			``GTrpl``   Galaxy Triplet
			``GGroup``  Group of galaxies
			``PN``      Planetary Nebula
			``HII``     HII Ionized region
			``DrkN``    Dark Nebula
			``EmN``     Emission Nebula
			``Neb``     Nebula
			``RfN``     Reflection Nebula
			``SNR``     Supernova remnant
			``Nova``    Nova star
			``NonEx``   Nonexistent object
			``Dup``     Duplicated object (cf. NGC or IC column)
			``Other``   See object notes
			=========== ========================================
		</meta>

		<meta name="note" tag="s">
The numbers correspond to the following resources:

== ==============================================
1  `NASA Extragalactic Database`_
2  `SIMBAD Astronomical Database`_
3  HyperLeda (:bibcode:`2003A&amp;A...412...45P`)
4  Harold Corwin's NGC/IC Positions and Notes
5  `HEASARC mwsc table`_
6  `HEASARC smcclustrs table`_
7  `HEASARC lmcextobj table`_
8  `HEASARC plnebulae table`_
9  `HEASARC lbn table`_
10 `HEASARC messier table`_
11 `HEASARC lyngaclust table`_
99 OpenNGC revised data
== ==============================================

.. _NASA Extragalactic Database: http://ned.ipac.caltech.edu/forms/data.html
.. _SIMBAD Astronomical Database: http://simbad.u-strasbg.fr/simbad/sim-fbasic
.. _HEASARC mwsc table: https://heasarc.gsfc.nasa.gov/W3Browse/all/mwsc.html
.. _HEASARC smcclustrs table: https://heasarc.gsfc.nasa.gov/W3Browse/all/smcclustrs.html
.. _HEASARC lmcextobj table: https://heasarc.gsfc.nasa.gov/W3Browse/all/lmcextobj.html
.. _HEASARC plnebulae table: https://heasarc.gsfc.nasa.gov/W3Browse/all/plnebulae.html
.. _HEASARC lbn table: https://heasarc.gsfc.nasa.gov/W3Browse/all/lbn.html
.. _HEASARC messier table: https://heasarc.gsfc.nasa.gov/W3Browse/all/messier.html
.. _HEASARC lyngaclust table: https://heasarc.gsfc.nasa.gov/W3Browse/all/lyngaclust.html

		</meta>
	</table>

	<coverage>
		<updater sourceTable="data"/>
	</coverage>

	<data id="import">
		<sources pattern="data/database_files/NGC.csv"/>
		<csvGrammar delimiter=";" enc="utf-8"/>
		<make table="data">
			<rowmaker>
				<LOOP>
					<csvItems>
						dest, src
						name, Name
						obj_type, Type
						constellation, Const
						pos_ang, PosAng
						mag_b, B-Mag
						mag_v, V-Mag
						mag_j, J-Mag
						mag_h, H-Mag
						mag_k, K-Mag
						surf_br_B, SurfBr
						cstar_mag_u, Cstar U-Mag
						cstar_mag_b, Cstar B-Mag
						cstar_mag_v, Cstar V-Mag
						messier_nr, M
						other_ngc, NGC
						ic_cross, IC
						cstar_id, Cstar Names
						other_id, Identifiers
						comname, Common names
						ned_notes, NED notes
						notes, OpenNGC notes
						pmra, Pm-RA
						pmdec, Pm-Dec
						rv, RadVel
						z, Redshift
					</csvItems>
					<events>
						<map key="\dest" source="\src"/>
					</events>
				</LOOP>
				<map key="hubble_type">parseWithNull(@Hubble, str, nullLiteral="")</map>
				<map key="maj_ax_deg" nullExcs="ValueError">float(@MajAx)/60.</map>
				<map key="min_ax_deg" nullExcs="ValueError">float(@MinAx)/60.</map>
				<map key="raj2000" nullExcs="ValueError"
					>hmsToDeg(@RA, sepChar=":")</map>
				<map key="dej2000" nullExcs="ValueError"
					>dmsToDeg(@Dec, sepChar=":")</map>
				<map key="sources">@Sources.replace("|", " ") or None</map>
				<LOOP listItems="comname cstar_id other_id ned_notes notes">
					<events>
						<map key="\item" nullExpr="''"/>
					</events>
				</LOOP>
			</rowmaker>
		</make>
	</data>

	<table id="shapes" onDisk="True" primary="name" adql="True">
		<meta name="description">
			Hand-drawn coverages of selected openngc objects.
			Up to three coverages at different surface brightness are obtained
			using the default DSS2 Color layer in Aladin.
			Usually, the level 2 shape is taken with default histogram, while level 1
			and level 3 use, respectvely, the first and the last half of the
			histogram.
		</meta>
		<foreignKey inTable="data" source="name" metaOnly="True"/>
		<column name="name" type="text"
			ucd="meta.id"
			tablehead="Object name"
			description="Name of the (main) object the coverage is for."
			verbLevel="1"/>
		<column name="shape_level1" type="smoc"
			ucd="phys.angArea"
			tablehead="Level 1 MOC"
			description="Coverage obtained by tracing the object's
				outline visually in DSS colour, with full output intensity
				at half saturation."
			verbLevel="15"/>
		<column name="shape_level2" type="smoc"
			ucd="phys.angArea"
			tablehead="Level 2 MOC"
			description="Coverage obtained by tracing the object's
				outline visually in DSS colour, with zero output intensity
				at zero saturation and full output intensity
				at full saturation."
			verbLevel="15"/>
		<column name="shape_level3" type="smoc"
			ucd="phys.angArea"
			tablehead="Level 3 MOC"
			description="Coverage obtained by tracing the object's
				outline visually in DSS colour, with zero output intensity
				at half saturation and full output intensity at
				full saturation."
			verbLevel="15"/>
		<column name="deepest_shape" type="smoc"
			ucd="phys.angArea;meta.main"
			description="Deepest coverage available.  As opposed to the
				other shape columns, this will always contain something as long
				as we have some coverage information; this is provided for
				convenience."
			verbLevel="5"/>
	</table>

	<data id="import_shapes">
		<sources pattern="data/outlines/objects/*.txt"/>
		<embeddedGrammar>
			<!-- I think these are Aladin shape files; we parse them very shallowly,
			just noticing when there's a new polygon starting. -->
			<iterator>
				<setup>
					<par name="seenObjs">set()</par>
					<par name="mocOrder">12</par>
					<code>
					def parseShapeFile(f):
						polys = [[]]
						for lnNo, ln in enumerate(f):
							if ln.startswith("line"):
								parts = ln.split()
								if parts[1] in '*+.':
									polys[-1].extend((float(parts[2]), float(parts[3])))
								else:
									raise utils.SourceParseError("Bad line",
										offending=ln, location=str(lnNo+1),
										source=f.name)

								if parts[1]=='*':
									polys.append([])
					
						# For some odd reason, the first point in each shape file appears
						# to be duplicated.  Chuck it for now.
						polys[0][:2] = []

						if not polys[-1]:
							del polys[-1]
						

						parts = [
							getASCIIMOCForPoly(pgsphere.SPoly.fromDALI(p))
							for p in polys]
						return " ".join(parts)+" "+str(mocOrder)+"/"

					def parseShapeFor(ri, objName, level):
						ri.sourceToken = rd.getAbsPath(
							"data/outlines/objects/%s_lv%s.txt"%(objName, level))
						try:
							with open(ri.sourceToken, "r") as f:
								return parseShapeFile(f)
						except IOError:
							# we believe the shape doesn't exist
							return None

					def parseShapeFileName(fName):
						mat = re.match("(.*)_lv([0-9]).txt", os.path.basename(fName))
						return mat.group(1), mat.group(2)

					# The following is lifted from cdshealpix's healpix.py;
					# The C++ healpix code can't deal with the polygons here,
					# and thus we're using the rust version (which this expects
					# in cdshealpix/; that's not in the VCS).
					from cffi import FFI
					ffi = FFI()
					ffi.cdef("""
						typedef struct {
							uint8_t depth;
							uint64_t hash;
							uint8_t flag;
						} bmoc_cell;

						typedef struct {
							uint32_t ncells;
							bmoc_cell* cells;
						} bmoc;

						void bmoc_free(bmoc* bmoc);
						bmoc* hpx_query_polygon_approx(unsigned char depth,
							int n_vertices, double* vertices_coords);""")

					cdshealpix = ffi.dlopen(rd.getAbsPath(
						"cdshealpix/libcdshealpix_ffi.so"))
					
					def getASCIIMOCForPoly(poly):
						"""returns an ASCII MOC for an SPoly p.
						"""
						coos = []
						for pt in poly.points:
							coos.extend([pt.x, pt.y])
						
						cells = cdshealpix.hpx_query_polygon_approx(
							mocOrder,
							len(coos)//2,
							coos)
						parts = []
						for i in range(cells.ncells):
							parts.append("%s/%s"%(
								cells.cells[i].depth,
								cells.cells[i].hash))
						cdshealpix.bmoc_free(cells)
						return " ".join(parts)
					</code>
				</setup>
				<code>
					objectName, level = parseShapeFileName(self.sourceToken)
					if objectName in seenObjs:
						return
					
					seenObjs.add(objectName)
					rec = {"name": objectName, "deepest_shape": None}
					for level in range(1,4):
						rec["shape_level%d"%level] = parseShapeFor(self, objectName, level)
						if rec["deepest_shape"] is None:
							rec["deepest_shape"] = rec["shape_level%d"%level]
					yield rec
				</code>
			</iterator>
		</embeddedGrammar>

		<make table="shapes"/>
	</data>

	<service id="web" allowed="form,scs.xml">
		<meta name="_intro" format="rst"><![CDATA[
			If you want to do any non-trivial querying of OpenNGC, please
			use `TAP </tap>`_.
		]]></meta>
		<publish sets="ivo_managed,local" render="form"/>
		<publish sets="ivo_managed" render="scs.xml"/>
		<meta>
			date: 2023-11-08
			date.role: ExportRequested
		</meta>
		<meta name="shortName">openNGC</meta>

		<meta name="testQuery.ra">202.46</meta>
		<meta name="testQuery.dec">47.2</meta>
		<meta name="testQuery.sr">0.1</meta>

		<scsCore queriedTable="data">
			<FEED source="//scs#coreDescs"/>
			<condDesc buildFrom="obj_type"/>
			<condDesc buildFrom="hubble_type"/>
			<condDesc buildFrom="name"/>
			<condDesc buildFrom="comname"/>
			<condDesc buildFrom="mag_b"/>
			<condDesc buildFrom="mag_v"/>
			<condDesc buildFrom="mag_k"/>
		</scsCore>
	</service>

	<regSuite title="Openngc">
		<regTest title="openngc data present">
			<url parSet="TAP"
				QUERY="SELECT * FROM openngc.data WHERE messier_nr=1">/tap/sync</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertEqual(row["constellation"], "Tau")
				self.assertAlmostEqual(row["raj2000"], 83.633208333)
				self.assertAlmostEqual(row["maj_ax_deg"], 0.13333334)
				self.assertAlmostEqual(row["name"], "NGC1952")
			</code>
		</regTest>

		<regTest title="openngc shapes do not intersect">
			<url parSet="TAP" query="select a.name, b.name
from openngc.shapes as a
join openngc.shapes as b
on (a.name&lt;b.name)
where 1=intersects(a.shape_level1, b.shape_level1)
and a.name!='IC2128'">/tap/sync</url>
			<code>
				self.assertEqual(self.getVOTableRows(), [])
			</code>
		</regTest>

		<regTest title="OpenNGC web">
			<url parSet="form" hscs_pos="M51" hscs_sr="10.0"
				hubble="SBa">/openngc/q/web/form</url>
			<code>
				self.assertHasStrings("NGC5195", "571.0", "5.50", " V-Mag:3 ")
			</code>
		</regTest>

		<regTest title="OpenNGC SCS gives some data">
			<url SR="0.1" RA="202.46" DEC="47.2">
				/openngc/q/web/scs.xml</url>
			<code>
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(row["z"], 0.0015500000)
				self.assertAlmostEqual(row["maj_ax_deg"], 0.22849999368190765)
			</code>
		</regTest>

	</regSuite>
</resource>
