diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java index 02ef467248230c3d94f83e02b831c22faedf7cd1..a109ed11982e6126913cc31de7aa64fec140524c 100644 --- a/src/tap/db/JDBCConnection.java +++ b/src/tap/db/JDBCConnection.java @@ -53,6 +53,7 @@ import tap.data.ResultSetTableIterator; import tap.data.TableIterator; import tap.log.TAPLog; import tap.metadata.TAPColumn; +import tap.metadata.TAPCoosys; import tap.metadata.TAPForeignKey; import tap.metadata.TAPMetadata; import tap.metadata.TAPMetadata.STDSchema; @@ -939,10 +940,18 @@ public class JDBCConnection implements DBConnection { logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.tables.", null); List<TAPTable> lstTables = loadTables(tap_schema.getTable(STDTable.TABLES.label), metadata, stmt); + // load all coordinate systems from TAP_SCHEMA.coosys: [non standard] + Map<String, TAPCoosys> mapCoosys = null; + if (isTableExisting(tap_schema.getDBName(), STDTable.COOSYS.label, stmt.getConnection().getMetaData())){ + if (logger != null) + logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.coosys.", null); + mapCoosys = loadCoosys(tap_schema.getTable(STDTable.COOSYS.label), metadata, stmt); + } + // load all columns from TAP_SCHEMA.columns: if (logger != null) logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.columns.", null); - loadColumns(tap_schema.getTable(STDTable.COLUMNS.label), lstTables, stmt); + loadColumns(tap_schema.getTable(STDTable.COLUMNS.label), lstTables, mapCoosys, stmt); // load all foreign keys from TAP_SCHEMA.keys and TAP_SCHEMA.key_columns: if (logger != null) @@ -1147,6 +1156,62 @@ public class JDBCConnection implements DBConnection { close(rs); } } + + /** + * Load all coordinate systems declared in the TAP_SCHEMA. + * + * @param tableDef Definition of the table TAP_SCHEMA.coosys. + * @param metadata Metadata in which the found coordinate systems will be inserted (see {@link TAPMetadata#addCoosys(TAPCoosys)}). + * @param stmt Statement to use in order to interact with the database. + * + * @return A map containing all declared coordinate systems (key=coosys ID, value={@link TAPCoosys}). + * <i>note: this map is required by {@link #loadColumns(TAPTable, List, Map, Statement)}.</i> + * + * @throws DBException If any error occurs while interacting with the database. + * + * @since 2.1 + */ + protected Map<String, TAPCoosys> loadCoosys(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException{ + ResultSet rs = null; + try{ + // Build the SQL query: + StringBuffer sqlBuf = new StringBuffer("SELECT "); + sqlBuf.append(translator.getColumnName(tableDef.getColumn("id"))); + sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("system"))); + sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("equinox"))); + sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("epoch"))); + sqlBuf.append(" FROM ").append(translator.getTableName(tableDef, supportsSchema)); + sqlBuf.append(" ORDER BY 1,2,3,4"); + + // Execute the query: + rs = stmt.executeQuery(sqlBuf.toString()); + + // Create all coosys: + HashMap<String, TAPCoosys> mapCoosys = new HashMap<String, TAPCoosys>(); + while(rs.next()){ + String coosysId = rs.getString(1), + system = rs.getString(2), equinox = rs.getString(3), + epoch = rs.getString(4); + + // create the new coosys: + TAPCoosys newCoosys = new TAPCoosys(coosysId, system, nullifyIfNeeded(equinox), nullifyIfNeeded(epoch)); + + // create and add the new coosys: + metadata.addCoosys(newCoosys); + mapCoosys.put(coosysId, newCoosys); + } + + return mapCoosys; + }catch(SQLException se){ + if (!isCancelled() && logger != null) + logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load coordinate systems from TAP_SCHEMA.coosys!", se); + throw new DBException("Impossible to load coordinate systems from TAP_SCHEMA.coosys!", se); + }finally{ + close(rs); + } + } + + /** * <p>Load into the corresponding tables all columns listed in TAP_SCHEMA.columns.</p> @@ -1166,8 +1231,38 @@ public class JDBCConnection implements DBConnection { * @param stmt Statement to use in order to interact with the database. * * @throws DBException If a table can not be found, or if any other error occurs while interacting with the database. + * + * @deprecated This method is now replaced by {@link #loadColumns(TAPTable, List, Map, Statement)} which has an additional parameter: + * the list of declared coordinate systems. */ + @Deprecated protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Statement stmt) throws DBException{ + loadColumns(tableDef, lstTables, null, stmt); + } + + /** + * <p>Load into the corresponding tables all columns listed in TAP_SCHEMA.columns.</p> + * + * <p><i>Note: + * Tables are searched in the given list by their ADQL name and case sensitively. + * If they can not be found a {@link DBException} is thrown. + * </i></p> + * + * <p><i>Note 2: + * If the column column_index exists, column entries are retrieved ordered by ascending table_name, then column_index, and finally column_name. + * If this column does not exist, column entries are retrieved ordered by ascending table_name and then column_name. + * </i></p> + * + * @param tableDef Definition of the table TAP_SCHEMA.columns. + * @param lstTables List of all published tables (= all tables listed in TAP_SCHEMA.tables). + * @param mapCoosys List of all published coordinate systems (= all coordinates systems listed in TAP_SCHEMA.coosys). + * @param stmt Statement to use in order to interact with the database. + * + * @throws DBException If a table can not be found, or if any other error occurs while interacting with the database. + * + * @since 2.1 + */ + protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Map<String, TAPCoosys> mapCoosys, final Statement stmt) throws DBException{ ResultSet rs = null; try{ // Determine whether the dbName column exists: @@ -1179,6 +1274,9 @@ public class JDBCConnection implements DBConnection { // Determine whether the columnIndex column exists: boolean hasColumnIndex = isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), "column_index", connection.getMetaData()); + // Determine whether the coosys_id column exists: + boolean hasCoosys = (mapCoosys != null) && isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), "coosys_id", connection.getMetaData()); + // Build the SQL query: StringBuffer sqlBuf = new StringBuffer("SELECT "); sqlBuf.append(translator.getColumnName(tableDef.getColumn("table_name"))); @@ -1201,6 +1299,8 @@ public class JDBCConnection implements DBConnection { sqlBuf.append(", "); translator.appendIdentifier(sqlBuf, DB_NAME_COLUMN, IdentifierField.COLUMN); } + if (hasCoosys) + sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("coosys_id"))); sqlBuf.append(" FROM ").append(translator.getTableName(tableDef, supportsSchema)); if (hasColumnIndex) sqlBuf.append(" ORDER BY 1,12,2"); @@ -1254,6 +1354,21 @@ public class JDBCConnection implements DBConnection { newColumn.setStd(std); newColumn.setDBName(dbName); newColumn.setIndex(colIndex); + + // set the coordinate system if any is specified: + if (hasCoosys){ + int indCoosys = 12; + if (hasColumnIndex) + indCoosys++; + if (hasDBName) + indCoosys++; + String coosysId = rs.getString(indCoosys); + if (coosysId != null){ + newColumn.setCoosys(mapCoosys.get(coosysId)); + if (logger != null && newColumn.getCoosys() == null) + logger.logDB(LogLevel.WARNING, this, "LOAD_TAP_SCHEMA", "No coordinate system for the column \""+columnName+"\"! Cause: unknown coordinate system: \""+coosysId+"\".", null); + } + } // add the new column inside its corresponding table: table.addColumn(newColumn); diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index 37c185bd14183000575d94d1f590d980a7f039eb..ccee023a839175f51337f6b4ab23856b0af9c106 100644 --- a/src/tap/formatter/VOTableFormat.java +++ b/src/tap/formatter/VOTableFormat.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -35,16 +36,17 @@ import tap.data.DataReadException; import tap.data.TableIterator; import tap.error.DefaultTAPErrorWriter; import tap.metadata.TAPColumn; +import tap.metadata.TAPCoosys; import tap.metadata.VotType; import tap.metadata.VotType.VotDatatype; import uk.ac.starlink.table.AbstractStarTable; import uk.ac.starlink.table.ColumnInfo; -import uk.ac.starlink.table.DefaultValueInfo; import uk.ac.starlink.table.DescribedValue; import uk.ac.starlink.table.RowSequence; import uk.ac.starlink.table.StarTable; import uk.ac.starlink.votable.DataFormat; import uk.ac.starlink.votable.VOSerializer; +import uk.ac.starlink.votable.VOStarTable; import uk.ac.starlink.votable.VOTableVersion; import adql.db.DBColumn; import adql.db.DBType; @@ -403,12 +405,31 @@ public class VOTableFormat implements OutputFormat { out.write("<INFO name=\"QUERY\"" + VOSerializer.formatAttribute("value", adqlQuery) + "/>"); out.newLine(); } - - /* TODO Add somewhere in the table header the different Coordinate Systems used in this result! - * 2 ways to do so: - * 1/ COOSYS (deprecated from VOTable 1.2, but soon un-deprecated) - * 2/ a GROUP item with the STC expression of the coordinate system. - */ + + // Insert the definition of all used coordinate systems: + HashSet<String> insertedCoosys = new HashSet<String>(10); + for(DBColumn col : execReport.resultingColumns){ + // ignore columns with no coossys: + if (col instanceof TAPColumn && ((TAPColumn)col).getCoosys() != null){ + // get its coosys: + TAPCoosys coosys = ((TAPColumn)col).getCoosys(); + // insert the coosys definition ONLY if not already done because of another column: + if (!insertedCoosys.contains(coosys.getId())){ + // write the VOTable serialization of this coordinate system definition: + out.write("<COOSYS"+VOSerializer.formatAttribute("ID", coosys.getId())); + if (coosys.getSystem() != null) + out.write(VOSerializer.formatAttribute("system", coosys.getSystem())); + if (coosys.getEquinox() != null) + out.write(VOSerializer.formatAttribute("equinox", coosys.getEquinox())); + if (coosys.getEpoch() != null) + out.write(VOSerializer.formatAttribute("epoch", coosys.getEpoch())); + out.write(" />"); + out.newLine(); + // remember this coosys has already been written: + insertedCoosys.add(coosys.getId()); + } + } + } out.flush(); } @@ -502,12 +523,16 @@ public class VOTableFormat implements OutputFormat { // Set the XType (if any): if (votType.xtype != null) - colInfo.setAuxDatum(new DescribedValue(new DefaultValueInfo("xtype", String.class, "VOTable xtype attribute"), votType.xtype)); + colInfo.setAuxDatum(new DescribedValue(VOStarTable.XTYPE_INFO, votType.xtype)); // Set the additional information: unit, UCD and UType: colInfo.setUnitString(tapCol.getUnit()); colInfo.setUCD(tapCol.getUcd()); colInfo.setUtype(tapCol.getUtype()); + + // Set the coosys ref (if any): + if (tapCol.getCoosys() != null) + colInfo.setAuxDatum(new DescribedValue(VOStarTable.REF_INFO, tapCol.getCoosys().getId())); return colInfo; } diff --git a/src/tap/metadata/TAPColumn.java b/src/tap/metadata/TAPColumn.java index 4dbb7433f704ba11fee8bbf4672d6b65299aa075..936909f0340d824fdfe90512e75ca070bb1730bb 100644 --- a/src/tap/metadata/TAPColumn.java +++ b/src/tap/metadata/TAPColumn.java @@ -16,7 +16,7 @@ package tap.metadata; * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2016 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), + * Copyright 2012-2017 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -69,7 +69,7 @@ import adql.db.DBType.DBDatatype; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2016) + * @version 2.1 (07/2017) */ public class TAPColumn implements DBColumn { @@ -132,6 +132,11 @@ public class TAPColumn implements DBColumn { * <i>Note: Standard TAP column field since TAP 1.1.</i> * @since 2.1 */ private int index = -1; + + /** Coordinate system used by this column values. + * <i>Note: Of course, this attribute has to be set only on coordinate columns.</i> + * @since 2.1 */ + private TAPCoosys coosys = null; /** Let add some information in addition of the ones of the TAP protocol. * <i>Note: This object can be anything: an {@link Integer}, a {@link String}, a {@link Map}, a {@link List}, ... @@ -770,6 +775,28 @@ public class TAPColumn implements DBColumn { this.index = columnIndex; } + /** + * Get the used coordinate system. + * + * @return Its coordinate system. + * + * @since 2.1 + */ + public final TAPCoosys getCoosys(){ + return coosys; + } + + /** + * Set the the coordinate system to use. + * + * @param newCoosys Its new coordinate system. + * + * @since 2.1 + */ + public final void setCoosys(final TAPCoosys newCoosys){ + this.coosys = newCoosys; + } + /** * <p>Get the other (piece of) information associated with this column.</p> * diff --git a/src/tap/metadata/TAPCoosys.java b/src/tap/metadata/TAPCoosys.java new file mode 100644 index 0000000000000000000000000000000000000000..90e8a63892136204656bf4621b299576cee4ca4c --- /dev/null +++ b/src/tap/metadata/TAPCoosys.java @@ -0,0 +1,175 @@ +package tap.metadata; + +/* + * This file is part of TAPLibrary. + * + * TAPLibrary is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TAPLibrary is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright 2017 - Astronomisches Rechen Institut (ARI) + */ + +/** + * Definition of a coordinate system. + * + * <p> + * This object just annotates a {@link TAPColumn} object (see {@link TAPColumn#getCoosys()} + * and {@link TAPColumn#setCoosys(TAPCoosys)}). Its only purpose is to enrich the VOTable + * metadata so that VO clients (like Aladin and TOPCAT) can use the coordinates the most + * precisely as possible. + * </p> + * + * @author Grégory Mantelet (ARI) + * @version 2.1 (07/2017) + * @since 2.1 + */ +public class TAPCoosys { + + /** ID of this coordinate system definition. + * <p>It is particularly used in the VOTable to associate columns with it.</p> + * <i>Note: This attribute can NOT be NULL.</i> */ + protected final String id; + + /** Name of the coordinate system. + * <p> + * It should be a value among: + * "ICRS", "eq FK5", "eq FK4", "ecl FK4", "ecl FK5", + * "galactic", "supergalactic", "barycentric", "geo app" + * and a user-defined "xy" value. + * </p> */ + protected final String system; + + /** Equinox of this coordinate system. + * <p> + * This parameter required to fix the equatorial or ecliptic systems + * (as e.g. "J2000" as the default "eq FK5" or "B1950" as the default "eq FK4"). + * </p> */ + protected String equinox; + + /** Epoch at which the coordinates were measured. */ + protected String epoch; + + /** + * Create a minimum coordinate system definition. + * + * @param id ID of the definition to create. <i>(must NOT be NULL)</i> + * @param system Coordinate system. <i>(must NOT be NULL)</i> + * + * @throws NullPointerException If any of the given parameters is NULL or an empty string. + */ + public TAPCoosys(final String id, final String system) throws NullPointerException { + this(id, system, null, null); + } + + /** + * Create a coordinate system definition. + * + * <p> + * Only the ID and the system are required. + * The equinox and especially the epoch are optional. + * </p> + * + * @param id ID of the definition to create. <i>(must NOT be NULL)</i> + * @param system Coordinate system. <i>(must NOT be NULL)</i> + * @param equinox Equinox of this coordinate system. + * @param epoch Epoch at which the coordinates were measured. + * + * @throws NullPointerException If the ID or the system is NULL or an empty string. + */ + public TAPCoosys(final String id, final String system, final String equinox, final String epoch) throws NullPointerException { + if (id == null || id.trim().length() == 0) + throw new NullPointerException("Missing Coosys ID!"); + this.id = id; + + if (system == null || system.trim().length() == 0) + throw new NullPointerException("Missing coordinate system!"); + this.system = system; + + this.equinox = equinox; + this.epoch = epoch; + } + + /** + * Get the ID of this coordinate system definition. + * + * @return Its ID. <i>(can NOT be NULL)</i> + */ + public final String getId() { + return id; + } + + /** + * Get the coordinate system of this definition. + * + * @return Its system. <i>(can NOT be NULL)</i> + */ + public final String getSystem() { + return system; + } + + /** + * Get the equinox of this coordinate system. + * + * @return Its equinox. <i>(may be NULL)</i> + */ + public final String getEquinox() { + return equinox; + } + + /** + * Set the equinox of this coordinate system. + * + * @param equinox Its new equinox. <i>(may be NULL)</i> + */ + public void setEquinox(final String equinox) { + this.equinox = equinox; + } + + /** + * Get the epoch at which the coordinates were measured. + * + * @return Its equinox. <i>(may be NULL)</i> + */ + public final String getEpoch() { + return epoch; + } + + /** + * Set the epoch at which the coordinates were measured. + * + * @param epoch Its new epoch. <i>(may be NULL)</i> + */ + public void setEpoch(final String epoch) { + this.epoch = epoch; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj){ + if (!(obj instanceof TAPCoosys)) + return false; + + TAPCoosys coosys = (TAPCoosys)obj; + return coosys.getId().equals(getId()); + } + + @Override + public String toString(){ + return id; + } + +} diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java index d5672674c75e253c3595ec8771f35b27d66fea68..0858031b650918afc710fbaf77277b2624ba6dc0 100644 --- a/src/tap/metadata/TAPMetadata.java +++ b/src/tap/metadata/TAPMetadata.java @@ -23,6 +23,7 @@ package tap.metadata; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -75,6 +76,10 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour /** List of all schemas available through the TAP service. */ protected final Map<String,TAPSchema> schemas; + + /** List of all coordinate systems used by columns published in the TAP_SCHEMA. + * @since 2.1 */ + protected final Map<String, TAPCoosys> coordinateSystems; /** Part of the TAP URI which identify this TAP resource. * By default, it is the resource name ; so here, the corresponding TAP URI would be: "/tables". */ @@ -108,6 +113,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour */ public TAPMetadata(){ schemas = new LinkedHashMap<String,TAPSchema>(); + coordinateSystems = new HashMap<String, TAPCoosys>(); } /** @@ -424,6 +430,60 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour nbTables += s.getNbTables(); return nbTables; } + + /** + * Get the coordinate system definition associated with the given ID. + * + * @param coosysId ID of the coordinate system to get. <i>(case sensitive)</i> + * + * @return The corresponding coordinate system definition, + * or NULL if no match for the given ID. + * + * @since 2.1 + */ + public TAPCoosys getCoosys(final String coosysId) { + return (coosysId == null ? null : coordinateSystems.get(coosysId)); + } + + /** + * Add the given coordinate system definition. + * + * <p><b>Important:</b> + * If a coordinate system with the same ID (case sensitive) is already declared in + * this {@link TAPMetadata}, it will be replaced by the given one. The replaced + * coordinate system is returned by this function. + * </p> + * + * @param newCoosys The coordinate system definition to add. + * + * @return The coordinate system definition previously declared with the same ID in this {@link TAPMetadata}, + * or NULL if no coord. sys. was declared with this ID. + * + * @since 2.1 + */ + public TAPCoosys addCoosys(final TAPCoosys newCoosys) { + if (newCoosys == null) + return null; + else{ + TAPCoosys formerValue = coordinateSystems.get(newCoosys.getId()); + coordinateSystems.put(newCoosys.getId(), newCoosys); + return formerValue; + } + } + + /** + * Remove the coordinate system declared with the given ID. + * + * @param coosysId The ID of the coordinate system definition to remove. + * + * @return The removed coordinate system definition, + * or NULL if none is declared with the given ID. + * + * @since 2.1 + */ + public TAPCoosys removeCoosys(final String coosysId){ + return (coosysId == null) ? null : coordinateSystems.remove(coosysId); + } /** * Let iterating over the list of all tables contained in a given {@link TAPMetadata} object. @@ -871,6 +931,14 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour tables.addColumn("utype", new DBType(DBDatatype.VARCHAR), "UTYPE if table corresponds to a data model", null, null, null, false, false, true); return tables; + case COOSYS: + TAPTable coosys = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.COOSYS, TableType.table, "List of coordinate systems published used this TAP service.", null); + coosys.addColumn("id", new DBType(DBDatatype.VARCHAR), "ID of the coordinate system definition.", null, null, null, true, true, false); + coosys.addColumn("system", new DBType(DBDatatype.VARCHAR), "The coordinate system (among \"ICRS\", \"eq_FK5\", \"eq_FK4\", \"ecl_FK4\", \"ecl_FK5\", \"galactic\", \"supergalactic\").", null, null, null, false, false, false); + coosys.addColumn("equinox", new DBType(DBDatatype.VARCHAR), "Required to fix the equatorial or ecliptic systems (as e.g. \"J2000\" as the default \"eq_FK5\" or \"B1950\" as the default \"eq_FK4\").", null, null, null, false, false, false); + coosys.addColumn("epoch", new DBType(DBDatatype.VARCHAR), "Epoch of the positions (if necessary).", null, null, null, false, false, false); + return coosys; + case COLUMNS: TAPTable columns = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.COLUMNS, TableType.table, "List of columns of all tables listed in TAP_SCHEMA.TABLES and published in this TAP service.", null); columns.addColumn("column_index", new DBType(DBDatatype.INTEGER), "this index is used to recommend column ordering for clients", null, null, null, false, false, true); @@ -886,6 +954,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour columns.addColumn("indexed", new DBType(DBDatatype.INTEGER), "an indexed column; 1 means true, 0 means false", null, null, null, false, false, true); columns.addColumn("principal", new DBType(DBDatatype.INTEGER), "a principal column; 1 means true, 0 means false", null, null, null, false, false, true); columns.addColumn("std", new DBType(DBDatatype.INTEGER), "a standard column; 1 means true, 0 means false", null, null, null, false, false, true); + columns.addColumn("coosys_id", new DBType(DBDatatype.VARCHAR), "ID of the used coordinate systems (if any).", null, null, null, false, false, false); return columns; case KEYS: @@ -962,11 +1031,18 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour * Enumeration of all tables of TAP_SCHEMA. * * @author Grégory Mantelet (ARI) - * @version 2.0 (07/2014) + * @version 2.1 (07/2017) * @since 2.0 */ public enum STDTable{ - SCHEMAS("schemas"), TABLES("tables"), COLUMNS("columns"), KEYS("keys"), KEY_COLUMNS("key_columns"); + SCHEMAS("schemas"), + TABLES("tables"), + COLUMNS("columns"), + KEYS("keys"), + KEY_COLUMNS("key_columns"), + /** Non standard table but anyway expected by this library in order to associated coordinate columns with a coordinate system. + * @since 2.1 */ + COOSYS("coosys"); /** Real name of the table. */ public final String label; diff --git a/src/tap/metadata/tap_coosys.html b/src/tap/metadata/tap_coosys.html new file mode 100644 index 0000000000000000000000000000000000000000..1446eb0d70ea95f5623d45c06aa203fb9cf07e3e --- /dev/null +++ b/src/tap/metadata/tap_coosys.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>Coordinate system in TAP metadata</title> + </head> + <body> + <h1>How to declare coordinate system(s) in TAP metadata?</h1> + <p> + The TAP-1.0 standard does not define any way to convey the + coordinate system used by coordinate columns. However, this is + more and more required by recent catalogs using recent epoch + (e.g. 2015 for Gaia-DR1) or coordinate systems not expected by + default by most of the VO applications (e.g. something different + from ICRS J2000). + </p> + + <p> + In order to provide most accurate metadata as possible, the + TAPLibrary gives a way to specify the coordinate system of your + coordinates. Here is how to proceed in function of how you provide + your TAP metadata: + </p> + + <h3>In <code>TAP_SCHEMA</code></h3> + <p> + You must add a table in <code>TAP_SCHEMA</code>: + <code>coosys</code>. This table must have the following columns: + </p> + <ul> + <li><code>id</code>: VARCHAR <i>(ID of the coordinate system + definition as it must be in the VOTable)</i></li> + <li><code>system</code>: VARCHAR <i>(The coordinate system among: + <code>ICRS</code>, <code>eq_FK5</code>, <code>eq_FK4</code>, + <code>ecl_FK4</code>, <code>ecl_FK5</code>, + <code>galactic</code>, <code>supergalactic</code>)</i></li> + <li><code>equinox</code>: VARCHAR <i>(Required to fix the equatorial + or ecliptic systems (as e.g. <code>J2000</code> as the default + for <code>eq_FK5</code> or <code>B1950</code> as the default + for <code>eq_FK4</code>))</i></li> + <li><code>epoch</code>: VARCHAR <i>(Epoch of the positions + (if necessary))</i></li> + </ul> + <p> + Then, you need an additional column in + <code>TAP_SCHEMA.columns</code>: + </p> + <ul> + <li><code>coosys_id</code>: VARCHAR <i>(ID of the used coordinate + systems (if any))</i></li> + </ul> + <p> + Now, all you have to do, is add any coordinate system you need in + <code>TAP_SCHEMA.coosys</code> and set the appropriate coordinate + system ID in the column <code>coosys_id</code> of all columns using + one. Refresh the metadata of your TAP service and now a + <code>COOSSYS</code> item should be in the metadata part of your + VOTable results (only when there are coordinates with a specified + coordinate system). + </p> + + <h3>In an XML file</h3> + <p> + Unfortunately, the XML definition of the node <code>column</code> + does not provide any standard way to provide something as complex + as a coordinate system. So, either you keep not providing coordinate + systems in your TAP service, or you choose a different method of + metadata declaration. + </p> + <p><i>Tip: + Starting your TAP service with an XML file as way to provide + metadata will immediately create and initialize correctly the + <code>TAP_SCHEMA</code> schema in your database. So, if you want + to provide coordinate system information, all you have to do, is to + switch the metadata declaration method in order to use this newly + created <code>TAP_SCHEMA</code>. Then, just follow the steps listed + above for this method. + </i></p> + + <h3>Programmatically</h3> + <p> + To add a coordinate system to a column, you just have to use the + new function of <code>TAPColumn</code>: + <code>setCoosys(TAPCoosys)</code>. As in the + <code>TAP_SCHEMA</code> method, you can use the same + <code>TAPCoosys</code> object for several columns. + </p> + </body> +</html> \ No newline at end of file