You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/07/24 17:53:21 UTC
(sis) branch geoapi-4.0 updated (5b8757c47e -> f36aa98eee)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
from 5b8757c47e Partial update of the EPSG geodetic dataset from version 9.9.1 to version 12.013. The "source_geogcrs_code" column is renamed "base_crs_code", but a compatibility mode is kept if the previous name is detected.
new 058a84cae1 Support the `REALIZATION_METHOD_CODE` column of the "Datum" table of EPSG version 10+.
new 155af1559e Support the ANCHOR_EPOCH and FRAME_REFERENCE_EPOCH columns of the "Datum" table in EPSG version 10+.
new c9dfe193ec Support the `CONVENTIONAL_RS_CODE` column of the "Datum" table of EPSG version 10+.
new f36aa98eee More robust analysis of the EPSG schema in use. For example, the check for missing columns is repeated for each column instead of testing a sentinel value and assuming the same result for all. This analysis is done only once when opening a connection to the EPSG database.
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/apache/sis/temporal/LenientDateFormat.java | 4 +-
.../apache/sis/temporal/LenientDateFormatTest.java | 4 +
.../apache/sis/io/wkt/GeodeticObjectParser.java | 6 +-
.../referencing/datum/DefaultGeodeticDatum.java | 2 +-
.../referencing/datum/DefaultVerticalDatum.java | 10 +-
.../referencing/factory/AuthorityFactoryProxy.java | 11 +
.../factory/ConcurrentAuthorityFactory.java | 28 ++-
.../factory/GeodeticAuthorityFactory.java | 79 +++---
.../referencing/factory/sql/EPSGDataAccess.java | 277 ++++++++++++++-------
.../sis/referencing/factory/sql/SQLTranslator.java | 248 +++++++++---------
.../org/apache/sis/referencing/internal/Epoch.java | 2 -
.../referencing/internal/VerticalDatumTypes.java | 57 ++++-
.../internal/VerticalDatumTypesTest.java | 33 ++-
13 files changed, 483 insertions(+), 278 deletions(-)
(sis) 03/04: Support the `CONVENTIONAL_RS_CODE` column of the "Datum" table of EPSG version 10+.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit c9dfe193eca6928f27ceb071973d17f001bd0546
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Jul 24 17:10:12 2025 +0200
Support the `CONVENTIONAL_RS_CODE` column of the "Datum" table of EPSG version 10+.
---
.../referencing/factory/AuthorityFactoryProxy.java | 11 +++
.../factory/ConcurrentAuthorityFactory.java | 28 +++++++-
.../factory/GeodeticAuthorityFactory.java | 79 +++++++++++-----------
.../referencing/factory/sql/EPSGDataAccess.java | 79 ++++++++++++++++++----
.../sis/referencing/factory/sql/SQLTranslator.java | 1 +
5 files changed, 144 insertions(+), 54 deletions(-)
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
index df276fe9ca..96018ea2c8 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
@@ -202,6 +202,17 @@ abstract class AuthorityFactoryProxy<T> {
}
};
+ @SuppressWarnings("unchecked")
+ static final AuthorityFactoryProxy<DatumEnsemble<?>> ENSEMBLE =
+ new AuthorityFactoryProxy<DatumEnsemble<?>>((Class) DatumEnsemble.class, AuthorityFactoryIdentifier.Type.DATUM) {
+ @Override DatumEnsemble<?> create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
+ return factory.createDatumEnsemble(code);
+ }
+ @Override DatumEnsemble<?> createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
+ return datumFactory(factory).createDatumEnsemble(code);
+ }
+ };
+
static final AuthorityFactoryProxy<Datum> DATUM =
new AuthorityFactoryProxy<Datum>(Datum.class, AuthorityFactoryIdentifier.Type.DATUM) {
@Override Datum create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
index 7dda302217..5f8fd800ea 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
@@ -1093,7 +1093,33 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
}
/**
- * Returns an arbitrary datum from a code. The returned object will typically be an
+ * Returns an arbitrary datum ensemble from a code.
+ * The default implementation performs the following steps:
+ * <ul>
+ * <li>Return the cached instance for the given code if such instance already exists.</li>
+ * <li>Otherwise if the Data Access Object (DAO) overrides the {@code createDatumEnsemble(String)}
+ * method, invoke that method and cache the result for future use.</li>
+ * <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createDatumEnsemble(String)}
+ * method in the parent class. This allows to check if the more generic
+ * {@link #createObject(String)} method cached a value before to try that method.</li>
+ * </ul>
+ *
+ * @return the datum for the given code.
+ * @throws FactoryException if the object creation failed.
+ *
+ * @since 1.5
+ */
+ @Override
+ public DatumEnsemble<?> createDatumEnsemble(final String code) throws FactoryException {
+ if (isDefault(DatumEnsemble.class)) {
+ return super.createDatumEnsemble(code);
+ }
+ return create(AuthorityFactoryProxy.ENSEMBLE, code);
+ }
+
+ /**
+ * Returns an arbitrary datum from a code. The returned object will typically be a
+ * {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum} or {@link EngineeringDatum}.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index 8016fd352c..30ebddb624 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -524,6 +524,46 @@ public abstract class GeodeticAuthorityFactory extends AbstractFactory implement
return cast(EngineeringCRS.class, createCoordinateReferenceSystem(code), code);
}
+ /**
+ * Creates an arbitrary datum ensemble from a code.
+ * A datum ensemble is a collection of datums which for low accuracy requirements
+ * may be considered to be insignificantly different from each other.
+ *
+ * <h4>Examples</h4>
+ * The {@linkplain #getAuthorityCodes(Class) set of available codes} depends on the defining
+ * {@linkplain #getAuthority() authority} and the {@code GeodeticAuthorityFactory} subclass in use.
+ * A frequently used authority is "EPSG", which includes the following codes:
+ *
+ * <table class="sis">
+ * <caption>Authority codes examples</caption>
+ * <tr><th>Code</th> <th>Description</th></tr>
+ * <tr><td>EPSG:6326</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:6258</td> <td>European Terrestrial Reference System 1989</td></tr>
+ * </table>
+ *
+ * <h4>Default implementation</h4>
+ * The default implementation delegates to {@link #createDatum(String)} and casts the result.
+ * If the result cannot be cast, then a {@link NoSuchAuthorityCodeException} is thrown.
+ * This approach assumes that the datum ensemble implements also the {@link Datum} interface.
+ * It is the case of the {@link org.apache.sis.referencing.datum.DefaultDatumEnsemble} class.
+ *
+ * <p>This default implementation is unusual, but is convenient for the implementation strategy
+ * of Apache <abbr>SIS</abbr> and for the structure of the <abbr>EPSG</abbr> geodetic dataset,
+ * which uses the same {@code "Datum"} table for storing the properties of the two kinds of object.</p>
+ *
+ * @param code value allocated by authority.
+ * @return the datum ensemble for the given code.
+ * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
+ * @throws FactoryException if the object creation failed for some other reason.
+ *
+ * @see org.apache.sis.referencing.datum.DefaultDatumEnsemble
+ *
+ * @since 1.5
+ */
+ public DatumEnsemble<?> createDatumEnsemble(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+ return cast(DatumEnsemble.class, createDatum(code), code);
+ }
+
/**
* Creates an arbitrary datum from a code. The returned object will typically be an
* instance of {@link GeodeticDatum}, {@link VerticalDatum} or {@link TemporalDatum}.
@@ -708,45 +748,6 @@ public abstract class GeodeticAuthorityFactory extends AbstractFactory implement
return cast(EngineeringDatum.class, createDatum(code), code);
}
- /**
- * Creates an arbitrary datum ensemble from a code.
- * A datum ensemble is a collection of datums which for low accuracy requirements
- * may be considered to be insignificantly different from each other.
- *
- * <h4>Examples</h4>
- * The {@linkplain #getAuthorityCodes(Class) set of available codes} depends on the defining
- * {@linkplain #getAuthority() authority} and the {@code GeodeticAuthorityFactory} subclass in use.
- * A frequently used authority is "EPSG", which includes the following codes:
- *
- * <table class="sis">
- * <caption>Authority codes examples</caption>
- * <tr><th>Code</th> <th>Description</th></tr>
- * <tr><td>EPSG:6326</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:6258</td> <td>European Terrestrial Reference System 1989</td></tr>
- * </table>
- *
- * <h4>Default implementation</h4>
- * The default implementation delegates to {@link #createDatum(String)} and casts the result.
- * If the result cannot be cast, then a {@link NoSuchAuthorityCodeException} is thrown.
- * Note that the delegation to {@code createDatum(…)} works only if the implementation
- * stores datum and datum ensembles together. This is the case of the <abbr>EPSG</abbr>
- * geodetic dataset for instance. In such case, the datum ensemble can be returned as an
- * object that implements both the {@link Datum} and {@link DatumEnsemble} interfaces,
- * such as the {@link org.apache.sis.referencing.datum.DefaultDatumEnsemble} class.
- *
- * @param code value allocated by authority.
- * @return the datum for the given code.
- * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
- * @throws FactoryException if the object creation failed for some other reason.
- *
- * @see org.apache.sis.referencing.datum.DefaultDatumEnsemble
- *
- * @since 1.5
- */
- public DatumEnsemble<?> createDatumEnsemble(final String code) throws NoSuchAuthorityCodeException, FactoryException {
- return cast(DatumEnsemble.class, createDatum(code), code);
- }
-
/**
* Creates a geometric figure that can be used to describe the approximate shape of the earth.
* In mathematical terms, it is a surface formed by the rotation of an ellipse about its minor axis.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 71746b6337..1a43f3d334 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -258,6 +258,17 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
*/
private final Map<Integer, AxisName> axisNames = new HashMap<>();
+ /**
+ * Cache for conventional reference systems, which are instances of the generic {@link IdentifiedObject} type.
+ * This is not cached by {@code ConcurrentAuthorityFactory} because there is not yet a specific type for that.
+ * Since we are not using the shared cache, there is a possibility that many objects are created for the same
+ * conventional <abbr>RS</abbr> code. However, this duplication should not happen if each instance appears in
+ * only one datum ensemble created by {@link #createDatumEnsemble(Integer, Map)}.
+ *
+ * @see #createConventionalRS(Integer)
+ */
+ private final Map<Integer, IdentifiedObject> conventionalRS = new HashMap<>();
+
/**
* Cache for realization methods. This service is not provided by {@code ConcurrentAuthorityFactory}
* because the realization method table is specific to the <abbr>EPSG</abbr> authority factory.
@@ -1690,20 +1701,21 @@ codes: for (int i=0; i<codes.length; i++) {
ArgumentChecks.ensureNonNull("code", code);
Datum returnValue = null;
try (ResultSet result = executeQuery("Datum", "DATUM_CODE", "DATUM_NAME",
- "SELECT DATUM_CODE," + // [ 1]
- " DATUM_NAME," + // [ 2]
- " DATUM_TYPE," + // [ 3]
- " ORIGIN_DESCRIPTION," + // [ 4]
- " ANCHOR_EPOCH," + // [ 5]
- " FRAME_REFERENCE_EPOCH," + // [ 6] — NULL for static datum, non-null if dynamic.
- " PUBLICATION_DATE," + // [ 7] — Was REALIZATION_EPOCH in EPSG version 9.
- " AREA_OF_USE_CODE," + // [ 8] — Deprecated since EPSG version 10 (always null)
- " DATUM_SCOPE," + // [ 9]
- " REMARKS," + // [10]
- " DEPRECATED," + // [11]
- " ELLIPSOID_CODE," + // [12] — Only for geodetic type
- " PRIME_MERIDIAN_CODE," + // [13] — Only for geodetic type
- " REALIZATION_METHOD_CODE" + // [14] — Only for vertical type
+ "SELECT DATUM_CODE," + // [ 1]
+ " DATUM_NAME," + // [ 2]
+ " DATUM_TYPE," + // [ 3]
+ " ORIGIN_DESCRIPTION," + // [ 4]
+ " ANCHOR_EPOCH," + // [ 5]
+ " FRAME_REFERENCE_EPOCH," + // [ 6] — NULL for static datum, non-null if dynamic.
+ " PUBLICATION_DATE," + // [ 7] — Was REALIZATION_EPOCH in EPSG version 9.
+ " AREA_OF_USE_CODE," + // [ 8] — Deprecated since EPSG version 10 (always null)
+ " DATUM_SCOPE," + // [ 9]
+ " REMARKS," + // [10]
+ " DEPRECATED," + // [11]
+ " ELLIPSOID_CODE," + // [12] — Only for geodetic type
+ " PRIME_MERIDIAN_CODE," + // [13] — Only for geodetic type
+ " REALIZATION_METHOD_CODE," + // [14] — Only for vertical type
+ " CONVENTIONAL_RS_CODE" + // [15] — Only for members of an ensemble
" FROM \"Datum\"" +
" WHERE DATUM_CODE = ?", code))
{
@@ -1725,6 +1737,7 @@ codes: for (int i=0; i<codes.length; i++) {
properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
properties.put(Datum.ANCHOR_EPOCH_KEY, epoch);
properties.put(Datum.PUBLICATION_DATE_KEY, publish);
+ properties.put(Datum.CONVENTIONAL_RS_KEY, createConventionalRS(getOptionalInteger(result, 15)));
/*
* The following switch statement should have a case for all "epsg_datum_kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in this Java code are in lower cases.
@@ -1819,6 +1832,9 @@ codes: for (int i=0; i<codes.length; i++) {
* @param code value allocated by EPSG.
* @param properties properties to assign to the datum ensemble.
* @return the datum ensemble for the given code, or {@code null} if not found.
+ *
+ * @see #createDatum(String)
+ * @see #createDatumEnsemble(String)
*/
private DatumEnsemble<?> createDatumEnsemble(final Integer code, final Map<String,Object> properties)
throws SQLException, FactoryException
@@ -1851,6 +1867,41 @@ codes: for (int i=0; i<codes.length; i++) {
return owner.datumFactory.createDatumEnsemble(properties, members, PositionalAccuracyConstant.ensemble(accuracy));
}
+ /**
+ * Creates a conventional reference system from a code.
+ * All members of a datum ensemble shall have the same conventional reference system.
+ *
+ * @param code value allocated by EPSG, or {@code null} if none.
+ * @return the datum for the given code, or {@code null} if not found.
+ */
+ private IdentifiedObject createConventionalRS(final Integer code) throws SQLException, FactoryException {
+ assert Thread.holdsLock(this);
+ IdentifiedObject returnValue = conventionalRS.get(code);
+ if (returnValue == null && code != null) {
+ try (ResultSet result = executeQuery("ConventionalRS",
+ "SELECT CONVENTIONAL_RS_CODE," +
+ " CONVENTIONAL_RS_NAME," +
+ " REMARKS," +
+ " DEPRECATED" +
+ " FROM \"ConventionalRS\"" +
+ " WHERE CONVENTIONAL_RS_CODE = ?", code))
+ {
+ while (result.next()) {
+ final Integer epsg = getInteger (code, result, 1);
+ final String name = getString (code, result, 2);
+ final String remarks = getOptionalString (result, 3);
+ final boolean deprecated = getOptionalBoolean (result, 4);
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ Map<String,Object> properties = createProperties("ConventionalRS",
+ epsg, name, null, null, null, remarks, deprecated);
+ returnValue = ensureSingleton(new AbstractIdentifiedObject(properties), returnValue, code);
+ }
+ }
+ conventionalRS.put(code, returnValue);
+ }
+ return returnValue;
+ }
+
/**
* Returns Bursa-Wolf parameters for a geodetic reference frame.
* If the specified datum has no conversion information, then this method returns {@code null}.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index cd160a4feb..8e89ab2ca9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -388,6 +388,7 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
addMissingColumn ("ANCHOR_EPOCH", "DOUBLE PRECISION"); // In table "Datum".
addMissingColumn ("FRAME_REFERENCE_EPOCH", "DOUBLE PRECISION"); // In table "Datum".
addMissingColumn ("REALIZATION_METHOD_CODE", "INTEGER"); // In table "Datum".
+ addMissingColumn ("CONVENTIONAL_RS_CODE", "INTEGER"); // In table "Datum".
columnRenaming = Map.copyOf(columnRenaming);
}
/*
(sis) 04/04: More robust analysis of the EPSG schema in use. For example, the check for missing columns is repeated for each column instead of testing a sentinel value and assuming the same result for all. This analysis is done only once when opening a connection to the EPSG database.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit f36aa98eee88ee0dff54695eaaebf84da2527916
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Jul 24 19:51:37 2025 +0200
More robust analysis of the EPSG schema in use.
For example, the check for missing columns is repeated for each column
instead of testing a sentinel value and assuming the same result for all.
This analysis is done only once when opening a connection to the EPSG database.
---
.../sis/referencing/factory/sql/SQLTranslator.java | 264 ++++++++++-----------
1 file changed, 132 insertions(+), 132 deletions(-)
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index 8e89ab2ca9..4cf1d9c27e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -18,8 +18,9 @@ package org.apache.sis.referencing.factory.sql;
import java.util.Map;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Locale;
-import java.util.function.Function;
+import java.util.function.UnaryOperator;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -50,8 +51,8 @@ import org.apache.sis.referencing.internal.Resources;
*
* <ul>
* <li>{@code SELECT * FROM "Coordinate Reference System"} (no change)</li>
- * <li>{@code SELECT * FROM coordinatereferencesystem}</li>
- * <li>{@code SELECT * FROM epsg_coordinatereferencesystem}</li>
+ * <li>{@code SELECT * FROM CoordinateReferenceSystem} (without quote, so the case will be database-dependent)</li>
+ * <li>{@code SELECT * FROM epsg_CoordinateReferenceSystem} (same as above with {@code "epsg_"} prefix added)</li>
* </ul>
*
* Above possibilities differ in the letter cases, spaces and {@code "epsg_"} prefix (non-exhaustive).
@@ -118,7 +119,7 @@ import org.apache.sis.referencing.internal.Resources;
* @version 1.5
* @since 0.7
*/
-public class SQLTranslator implements Function<String,String> {
+public class SQLTranslator implements UnaryOperator<String> {
/**
* The prefix in table names. The SQL scripts are provided by EPSG with this prefix in front of all table names.
* SIS rather uses a modified version of those SQL scripts which creates the tables in an "EPSG" database schema.
@@ -128,22 +129,6 @@ public class SQLTranslator implements Function<String,String> {
*/
static final String TABLE_PREFIX = "epsg_";
- /**
- * Table names used as "sentinel value" for detecting the presence of an <abbr>EPSG</abbr> database.
- * This array lists different names that may be used for the same table. The names will be tested in
- * declaration order.
- */
- private static final String[] SENTINEL = {
- TABLE_PREFIX + "coordinatereferencesystem",
- "Coordinate Reference System", // The index of this entry must be declared in `MIXED_CASE`.
- "coordinatereferencesystem"
- };
-
- /**
- * Index of the {@link #SENTINEL} element which is in mixed case. No other element should be in mixed case.
- */
- private static final int MIXED_CASE = 1;
-
/**
* The columns that may be of {@code BOOLEAN} type instead of {@code SMALLINT}.
*/
@@ -216,7 +201,7 @@ public class SQLTranslator implements Function<String,String> {
* and values are the names used in the <abbr>EPSG</abbr> database on which {@link EPSGDataAccess} is connected.
* By convention, all column names are in upper-case while table names are in mixed-case characters.</p>
*/
- private Map<String,String> tableRenaming;
+ private Map<String,String> tableRewording;
/**
* Mapping from column names used by {@link EPSGDataAccess} to the names actually used by the database.
@@ -247,7 +232,7 @@ public class SQLTranslator implements Function<String,String> {
private boolean useBoolean;
/**
- * {@code true} if one of the {@link #SENTINEL} tables exist.
+ * Whether the sentinel table "Coordinate_Operation" or a variant has been found.
* If {@code false}, then {@link EPSGInstaller} needs to be run.
*
* @see #isTableFound()
@@ -294,148 +279,163 @@ public class SQLTranslator implements Function<String,String> {
* <li>Determine whether the table names are prefixed by {@value #TABLE_PREFIX}
* and whether table names are in lower-case or mixed-case.</li>
*
- * <li>Fill the {@link #tableRenaming} and {@link #columnRenaming} maps. These maps translate table
+ * <li>Fill the {@link #tableRewording} and {@link #columnRenaming} maps. These maps translate table
* and column names used in the <abbr>SQL</abbr> statements into the names used by the database.
* Two conventions are understood: the names used in the MS-Access database or the names used
* in the <abbr>SQL</abbr> scripts, potentially with {@linkplain #TABLE_PREFIX prefix} removed.</li>
* </ol>
*/
+ @SuppressWarnings("fallthrough")
final void setup(final DatabaseMetaData md) throws SQLException {
- final boolean toUpperCase = md.storesUpperCaseIdentifiers();
- final String escape = md.getSearchStringEscape();
+ final String escape = md.getSearchStringEscape();
String schemaPattern = SQLUtilities.escape(schema, escape);
- for (int i=0; i<SENTINEL.length; i++) {
- String table = SENTINEL[i];
- if (toUpperCase && i != MIXED_CASE) {
- table = table.toUpperCase(Locale.US);
+ int tableIndex = 0;
+ do {
+ usePrefixedTableNames = false;
+ useMixedCaseTableNames = false;
+ String table = "";
+ switch (tableIndex++) {
+ case 0: { // Test EPSG standard table name first.
+ usePrefixedTableNames = true;
+ table = SQLUtilities.escape(TABLE_PREFIX, escape);
+ // Fallthrough for testing "epsg_coordoperation".
+ }
+ case 2: {
+ table += "coordoperation"; // Prefixed by "epsg_" in case 0.
+ if (md.storesUpperCaseIdentifiers()) {
+ table = table.toUpperCase(Locale.US);
+ }
+ break;
+ }
+ case 1: { // Variant used by the Apache SIS installer.
+ useMixedCaseTableNames = true;
+ table = "Coordinate_Operation";
+ break;
+ }
+ default: return; // EPSG table not found.
}
try (ResultSet result = md.getTables(catalog, schemaPattern, table, null)) {
- if (result.next()) {
+ while (result.next()) {
isTableFound = true;
- usePrefixedTableNames = table.startsWith(TABLE_PREFIX);
- useMixedCaseTableNames = (i == MIXED_CASE);
- do {
- catalog = result.getString(Reflection.TABLE_CAT);
- schema = result.getString(Reflection.TABLE_SCHEM);
- } while (!Constants.EPSG.equalsIgnoreCase(schema) && result.next());
- if (schema == null) schema = "";
- schemaPattern = SQLUtilities.escape(schema, escape);
- break;
+ catalog = result.getString(Reflection.TABLE_CAT);
+ schema = result.getString(Reflection.TABLE_SCHEM);
+ if (result.wasNull()) schema = "";
+ if (Constants.EPSG.equalsIgnoreCase(schema)) {
+ break; // Give precedence to "epsg" schema.
+ }
}
}
- }
- tableRenaming = Map.of();
- columnRenaming = Map.of();
- if (!isTableFound) {
- return;
- }
+ } while (!isTableFound);
/*
- * At this point, the catalog and schema have been found or have been confirmed,
- * or are still null if we did not found the EPSG table used as a sentinel value.
- * The following map contains renaming not covered by the generic algorithm
- * implemented in `toActualTableName(…)`.
+ * At this point, we found the EPSG sentinel table and we identified the
+ * naming convention (unquoted or mixed-case, prefixed by "epsg_" or not).
*/
- if (!useMixedCaseTableNames) {
- tableRenaming = Map.of("Coordinate_Operation", "coordoperation",
- "Parameter", "param");
- }
- /*
- * Column name patterns which will be used in the rest of this method.
- * They need to be adapted to the letter case convention of the database.
- */
- String order = "ORDER";
- String baseCRS = "%CRS_CODE"; // "BASE_CRS_CODE" or "SOURCE_GEOGCRS_CODE".
- String deprecated = "DEPRECATED";
- String objectTable = ENUMERATION_COLUMN;
- if (md.storesLowerCaseIdentifiers()) {
- order = order.toLowerCase(Locale.US);
- baseCRS = baseCRS.toLowerCase(Locale.US);
- deprecated = deprecated.toLowerCase(Locale.US);
- objectTable = objectTable.toLowerCase(Locale.US);
- }
+ UnaryOperator<String> toNativeCase = UnaryOperator.identity();
+ schemaPattern = SQLUtilities.escape(schema, escape);
+ columnRenaming = new HashMap<>();
+ tableRewording = Map.of();
/*
- * MS-Access database uses a column named "ORDER" in the "Coordinate Axis" table.
- * This column has been renamed "coord_axis_order" in DLL scripts.
- * We need to check which name our current database uses.
+ * Special cases not covered by the generic algorithm implemented in `toActualTableName(…)`.
+ * The entries are actually not full table names, but words separated by space. For example,
+ * "Coordinate_Operation Parameter Value" will be renamed "epsg_CoordOperationParamValue"
+ * (note that this renaming combines the two entries of the map).
*/
- try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTableName("Coordinate Axis"), order)) {
- if (result.next()) {
- columnRenaming = Map.of("COORD_AXIS_ORDER", "ORDER");
+ if (!useMixedCaseTableNames) {
+ tableRewording = Map.of("Coordinate_Operation", "coordoperation",
+ "Parameter", "param");
+ if (md.storesLowerCaseIdentifiers()) {
+ toNativeCase = (table) -> table.toLowerCase(Locale.US);
+ } else if (md.storesUpperCaseIdentifiers()) {
+ toNativeCase = (table) -> table.toUpperCase(Locale.US);
}
}
/*
- * A column named "BASE_CRS_CODE" in EPSG version 9 has been renamed "SOURCE_GEOGCRS_CODE" in version 10.
- * Detects which name is used, with precedence to latest database version if the two columns are found.
- * Opportunistically use this detection for generating empty columns for fields that did not existed in
- * the "Datum" table of EPSG version 9.
+ * Check if some columns need to be renamed in SQL queries. For example, MS-Access database
+ * uses a column named "ORDER" instead of "COORD_AXIS_ORDER" in the "Coordinate Axis" table.
+ * The other changes are differences between EPSG version 9 and version 10. For example,
+ * column "BASE_CRS_CODE" in EPSG version 10 was named "SOURCE_GEOGCRS_CODE" in version 9.
+ *
+ * Furthermore, some columns in EPSG version 10 did not existed in EPSG version 9.
+ * This loop also checks for columns that do not exist, and replaces them by NULL constant.
*/
- boolean isOldSchema = false;
-skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTableName("Coordinate Reference System"), baseCRS)) {
- while (result.next()) {
- final String column = result.getString(Reflection.COLUMN_NAME);
- if ("BASE_CRS_CODE".equalsIgnoreCase(column)) {
- break skip; // Found the column of EPSG version 10.
+ final var missingColumns = new HashMap<String, String>();
+ final var mayRenameColumns = new HashMap<String, String>();
+ final var brokenTargetCols = new HashSet<String>();
+ tableIndex = 0;
+check: for (;;) {
+ String table;
+ switch (tableIndex++) {
+ case 0: {
+ table = "Coordinate Axis";
+ mayRenameColumns.put("COORD_AXIS_ORDER", "ORDER"); // SQL script → MS-Access.
+ break;
}
- if (!isOldSchema) {
- isOldSchema = "SOURCE_GEOGCRS_CODE".equalsIgnoreCase(column);
+ case 1: {
+ table = "Coordinate Reference System";
+ mayRenameColumns.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE"); // EPSG version 10 → version 9.
+ break;
}
- }
- }
- if (isOldSchema) {
- columnRenaming = new HashMap<>(columnRenaming);
- columnRenaming.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE"); // In table "Coordinate Reference System".
- columnRenaming.put("PUBLICATION_DATE", "REALIZATION_EPOCH"); // In table "Datum".
- addMissingColumn ("ANCHOR_EPOCH", "DOUBLE PRECISION"); // In table "Datum".
- addMissingColumn ("FRAME_REFERENCE_EPOCH", "DOUBLE PRECISION"); // In table "Datum".
- addMissingColumn ("REALIZATION_METHOD_CODE", "INTEGER"); // In table "Datum".
- addMissingColumn ("CONVENTIONAL_RS_CODE", "INTEGER"); // In table "Datum".
- columnRenaming = Map.copyOf(columnRenaming);
- }
- /*
- * Detect if the database uses boolean types where applicable.
- * We arbitrarily use the Datum table as a representative value.
- */
- final String tablePattern = usePrefixedTableNames ? SQLUtilities.escape(TABLE_PREFIX, escape) + '%' : null;
- try (ResultSet result = md.getColumns(catalog, schemaPattern, tablePattern, deprecated)) {
- while (result.next()) {
- if (CharSequences.endsWith(result.getString(Reflection.TABLE_NAME), "Datum", true)) {
- final int type = result.getInt(Reflection.DATA_TYPE);
- useBoolean = (type == Types.BOOLEAN) || (type == Types.BIT);
+ case 2: {
+ table = "Datum";
+ missingColumns.put("ANCHOR_EPOCH", "DOUBLE PRECISION");
+ missingColumns.put("FRAME_REFERENCE_EPOCH", "DOUBLE PRECISION");
+ missingColumns.put("REALIZATION_METHOD_CODE", "INTEGER");
+ missingColumns.put("CONVENTIONAL_RS_CODE", "INTEGER");
+ mayRenameColumns.put("PUBLICATION_DATE", "REALIZATION_EPOCH"); // EPSG version 10 → version 9.
break;
}
+ case 3: {
+ table = "Alias";
+ break;
+ }
+ default: break check;
}
- }
- /*
- * Detect if the tables use enumeration (on PostgreSQL database) instead of VARCHAR.
- * Enumerations appear in various tables, including in a WHERE clause for the Alias table.
- */
- try (ResultSet result = md.getColumns(catalog, schemaPattern, tablePattern, objectTable)) {
- while (result.next()) {
- if (CharSequences.endsWith(result.getString(Reflection.TABLE_NAME), "Alias", true)) {
- final String type = result.getString(Reflection.TYPE_NAME);
- if (!CharSequences.startsWith(type, "VARCHAR", true)) {
- tableNameEnum = type;
+ brokenTargetCols.addAll(mayRenameColumns.values());
+ table = toNativeCase.apply(toActualTableName(table));
+ try (ResultSet result = md.getColumns(catalog, schemaPattern, SQLUtilities.escape(table, escape), "%")) {
+ while (result.next()) {
+ final String column = result.getString(Reflection.COLUMN_NAME).toUpperCase(Locale.US);
+ missingColumns.remove(column);
+ if (mayRenameColumns.remove(column) == null) { // Do not rename if the new column exists.
+ brokenTargetCols.remove(column); // Remember which old names were found.
+ }
+ /*
+ * Detect if the database uses boolean types where applicable.
+ * We arbitrarily use the `deprecated` column as a representative value.
+ */
+ if ("DEPRECATED".equals(column)) {
+ final int type = result.getInt(Reflection.DATA_TYPE);
+ useBoolean |= (type == Types.BOOLEAN) || (type == Types.BIT);
+ }
+ /*
+ * Detect if the tables use enumeration (on PostgreSQL database) instead of VARCHAR.
+ * Enumerations appear in various tables, including in a WHERE clause for the Alias table.
+ */
+ if (ENUMERATION_COLUMN.equals(column)) {
+ final String type = result.getString(Reflection.TYPE_NAME);
+ if (!CharSequences.startsWith(type, "VARCHAR", true)) {
+ tableNameEnum = type;
+ }
}
- break;
}
}
+ missingColumns.forEach((column, type) -> {
+ columnRenaming.put(column, "CAST(NULL AS " + type + ") AS " + column);
+ });
+ mayRenameColumns.values().removeAll(brokenTargetCols); // For renaming only when the old name has been found.
+ columnRenaming.putAll(mayRenameColumns);
+ mayRenameColumns.clear();
+ brokenTargetCols.clear();
+ missingColumns.clear();
}
+ columnRenaming = Map.copyOf(columnRenaming);
sameQueries = (tableNameEnum == null)
- && tableRenaming.isEmpty()
+ && tableRewording.isEmpty()
&& columnRenaming.isEmpty()
&& "\"".equals(identifierQuote)
&& !useBoolean;
}
- /**
- * Declares that the column of the given name shall be replaced by a placeholder.
- * This method is invoked for columns added in <abbr>EPSG</abbr> version 10 or later
- * when we detected that the schema in use is <abbr>EPSG</abbr> version 8 or before;
- */
- private void addMissingColumn(final String column, final String type) {
- columnRenaming.put(column, "CAST(NULL AS " + type + ") AS " + column);
- }
-
/**
* Returns the catalog that contains the EPSG schema. This is the catalog specified at construction time
* if it was non-null, or the catalog discovered by the constructor otherwise.
@@ -463,7 +463,7 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
}
/**
- * Returns whether the EPSG tables have been found.
+ * Returns whether the <abbr>EPSG</abbr> tables have been found.
* If {@code false}, then {@link EPSGInstaller} needs to be run.
*/
final boolean isTableFound() {
@@ -484,7 +484,7 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
}
}
return Resources.forLocale(locale).getString(Resources.Keys.TableNotFound_3,
- Constants.EPSG, db, SENTINEL[MIXED_CASE]);
+ Constants.EPSG, db, "Coordinate_Operation");
}
/**
@@ -526,7 +526,7 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
buffer.append(TABLE_PREFIX);
}
for (final String word : name.split("\\s")) {
- buffer.append(tableRenaming.getOrDefault(word, word));
+ buffer.append(tableRewording.getOrDefault(word, word));
}
// Ignore lower/upper case.
}
(sis) 01/04: Support the `REALIZATION_METHOD_CODE` column of the "Datum" table of EPSG version 10+.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 058a84cae16e41f7c10922a9703957d19ba05e25
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Jul 24 12:09:15 2025 +0200
Support the `REALIZATION_METHOD_CODE` column of the "Datum" table of EPSG version 10+.
---
.../apache/sis/io/wkt/GeodeticObjectParser.java | 6 +-
.../referencing/datum/DefaultVerticalDatum.java | 8 +-
.../referencing/factory/sql/EPSGDataAccess.java | 87 ++++++++++++++++------
.../sis/referencing/factory/sql/SQLTranslator.java | 24 ++++--
.../org/apache/sis/referencing/internal/Epoch.java | 2 -
.../referencing/internal/VerticalDatumTypes.java | 57 ++++++++++----
.../internal/VerticalDatumTypesTest.java | 33 +++++---
7 files changed, 155 insertions(+), 62 deletions(-)
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 2b969c5339..fcc952c75c 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -1447,10 +1447,10 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo
@SuppressWarnings("deprecation")
RealizationMethod method = null;
if (isWKT1) {
- method = VerticalDatumTypes.fromLegacy(element.pullInteger("datum"));
+ method = VerticalDatumTypes.fromLegacyCode(element.pullInteger("datum"));
}
if (method == null) {
- method = VerticalDatumTypes.guess(name, null, null);
+ method = VerticalDatumTypes.fromDatum(name, null, null);
}
final DatumFactory datumFactory = factories.getDatumFactory();
try {
@@ -1936,7 +1936,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo
* more information. Verify if we can have a better type now, and if so rebuild the datum.
*/
if (datum.getRealizationMethod().isEmpty()) {
- var type = VerticalDatumTypes.guess(datum.getName().getCode(), datum.getAlias(), cs.getAxis(0));
+ var type = VerticalDatumTypes.fromDatum(datum.getName().getCode(), datum.getAlias(), cs.getAxis(0));
if (type != null) {
final DatumFactory datumFactory = factories.getDatumFactory();
datum = datumFactory.createVerticalDatum(IdentifiedObjects.getProperties(datum), type);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index c7ac53c321..4a097c0eed 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@ -98,6 +98,8 @@ public class DefaultVerticalDatum extends AbstractDatum implements VerticalDatum
/**
* The realization method (geoid, tidal, <i>etc.</i>), or {@code null} if unspecified.
+ *
+ * @see #getRealizationMethod()
*/
private RealizationMethod method;
@@ -366,7 +368,7 @@ public class DefaultVerticalDatum extends AbstractDatum implements VerticalDatum
protected String formatTo(final Formatter formatter) {
super.formatTo(formatter);
if (formatter.getConvention().majorVersion() == 1) {
- formatter.append(VerticalDatumTypes.toLegacy(method));
+ formatter.append(VerticalDatumTypes.toLegacyCode(method));
return WKTKeywords.Vert_Datum;
}
return formatter.shortOrLong(WKTKeywords.VDatum, WKTKeywords.VerticalDatum);
@@ -408,7 +410,7 @@ public class DefaultVerticalDatum extends AbstractDatum implements VerticalDatum
if (Context.isGMLVersion(Context.current(), LegacyNamespaces.VERSION_3_2)) {
return null;
}
- return VerticalDatumTypes.toName(method);
+ return VerticalDatumTypes.toLegacyName(method);
}
/**
@@ -416,7 +418,7 @@ public class DefaultVerticalDatum extends AbstractDatum implements VerticalDatum
*/
private void setTypeElement(final String type) {
if (method == null) {
- method = VerticalDatumTypes.fromName(type);
+ method = VerticalDatumTypes.fromLegacyName(type);
} else {
ImplementationHelper.propertyAlreadySet(DefaultVerticalDatum.class, "setType", "verticalDatumType");
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 0b3b995688..e2cef4ba6a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -88,6 +88,7 @@ import org.apache.sis.referencing.internal.EPSGParameterDomain;
import org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
import org.apache.sis.referencing.internal.SignReversalComment;
+import org.apache.sis.referencing.internal.VerticalDatumTypes;
import org.apache.sis.referencing.internal.Resources;
import org.apache.sis.referencing.internal.ServicesForMetadata;
import org.apache.sis.parameter.DefaultParameterDescriptor;
@@ -239,7 +240,7 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
* A pool of prepared statements. Keys are {@link String} objects related to their originating method
* (for example "Ellipsoid" for {@link #createEllipsoid(String)}).
*/
- private final Map<String,PreparedStatement> statements = new HashMap<>();
+ private final Map<String, PreparedStatement> statements = new HashMap<>();
/**
* The set of authority codes for different types. This map is used by the {@link #getAuthorityCodes(Class)}
@@ -265,7 +266,15 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
*
* @see #getAxisName(Integer)
*/
- private final Map<Integer,AxisName> axisNames = new HashMap<>();
+ private final Map<Integer, AxisName> axisNames = new HashMap<>();
+
+ /**
+ * Cache for realization methods. This service is not provided by {@code ConcurrentAuthorityFactory}
+ * because the realization method table is specific to the <abbr>EPSG</abbr> authority factory.
+ *
+ * @see #getRealizationMethod(Integer)
+ */
+ private final Map<Integer, RealizationMethod> realizationMethods = new HashMap<>();
/**
* Cache of naming systems other than EPSG. There is usually few of them (at most 15).
@@ -273,13 +282,13 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
*
* @see #createProperties(String, Integer, String, CharSequence, String, String, CharSequence, boolean)
*/
- private final Map<String,NameSpace> namingSystems = new HashMap<>();
+ private final Map<String, NameSpace> namingSystems = new HashMap<>();
/**
* The properties to be given the objects to construct.
* Reused every time {@code createProperties(…)} is invoked.
*/
- private final Map<String,Object> properties = new HashMap<>();
+ private final Map<String, Object> properties = new HashMap<>();
/**
* A safety guard for preventing never-ending loops in recursive calls to some {@code createFoo(String)} methods.
@@ -293,7 +302,7 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
*
* Keys are EPSG codes and values are the type of object being constructed (but those values are not yet used).
*/
- private final Map<Integer,Class<?>> safetyGuard = new HashMap<>();
+ private final Map<Integer, Class<?>> safetyGuard = new HashMap<>();
/**
* {@code true} for disabling the logging of warnings when this factory creates deprecated objects.
@@ -1396,7 +1405,7 @@ codes: for (int i=0; i<codes.length; i++) {
endOfRecursion(GeographicCRS.class, epsg);
}
}
- DatumEnsemble<GeodeticDatum> ensemble = tryAsEnsemble(datum, GeodeticDatum.class);
+ DatumEnsemble<GeodeticDatum> ensemble = wasDatumEnsemble(datum, GeodeticDatum.class);
if (ensemble != null) datum = null;
crs = crsFactory.createGeographicCRS(createProperties("Coordinate Reference System",
epsg, name, null, area, scope, remarks, deprecated), datum, ensemble, cs);
@@ -1493,7 +1502,7 @@ codes: for (int i=0; i<codes.length; i++) {
case "vertical": {
VerticalCS cs = owner.createVerticalCS (getString(code, result, 8));
VerticalDatum datum = owner.createVerticalDatum(getString(code, result, 9));
- DatumEnsemble<VerticalDatum> ensemble = tryAsEnsemble(datum, VerticalDatum.class);
+ DatumEnsemble<VerticalDatum> ensemble = wasDatumEnsemble(datum, VerticalDatum.class);
if (ensemble != null) datum = null;
crs = crsFactory.createVerticalCRS(createProperties("Coordinate Reference System",
epsg, name, null, area, scope, remarks, deprecated), datum, ensemble, cs);
@@ -1509,7 +1518,7 @@ codes: for (int i=0; i<codes.length; i++) {
case "temporal": {
TimeCS cs = owner.createTimeCS (getString(code, result, 8));
TemporalDatum datum = owner.createTemporalDatum(getString(code, result, 9));
- DatumEnsemble<TemporalDatum> ensemble = tryAsEnsemble(datum, TemporalDatum.class);
+ DatumEnsemble<TemporalDatum> ensemble = wasDatumEnsemble(datum, TemporalDatum.class);
if (ensemble != null) datum = null;
crs = crsFactory.createTemporalCRS(createProperties("Coordinate Reference System",
epsg, name, null, area, scope, remarks, deprecated), datum, ensemble, cs);
@@ -1544,7 +1553,7 @@ codes: for (int i=0; i<codes.length; i++) {
case "geocentric": {
CoordinateSystem cs = owner.createCoordinateSystem(getString(code, result, 8));
GeodeticDatum datum = owner.createGeodeticDatum (getString(code, result, 9));
- DatumEnsemble<GeodeticDatum> ensemble = tryAsEnsemble(datum, GeodeticDatum.class);
+ DatumEnsemble<GeodeticDatum> ensemble = wasDatumEnsemble(datum, GeodeticDatum.class);
if (ensemble != null) datum = null;
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties("Coordinate Reference System",
@@ -1565,7 +1574,7 @@ codes: for (int i=0; i<codes.length; i++) {
case "engineering": {
CoordinateSystem cs = owner.createCoordinateSystem(getString(code, result, 8));
EngineeringDatum datum = owner.createEngineeringDatum(getString(code, result, 9));
- DatumEnsemble<EngineeringDatum> ensemble = tryAsEnsemble(datum, EngineeringDatum.class);
+ DatumEnsemble<EngineeringDatum> ensemble = wasDatumEnsemble(datum, EngineeringDatum.class);
if (ensemble != null) datum = null;
crs = crsFactory.createEngineeringCRS(createProperties("Coordinate Reference System",
epsg, name, null, area, scope, remarks, deprecated), datum, ensemble, cs);
@@ -1577,7 +1586,7 @@ codes: for (int i=0; i<codes.length; i++) {
case "parametric": {
ParametricCS cs = owner.createParametricCS (getString(code, result, 8));
ParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
- DatumEnsemble<ParametricDatum> ensemble = tryAsEnsemble(datum, ParametricDatum.class);
+ DatumEnsemble<ParametricDatum> ensemble = wasDatumEnsemble(datum, ParametricDatum.class);
if (ensemble != null) datum = null;
crs = crsFactory.createParametricCRS(createProperties("Coordinate Reference System",
epsg, name, null, area, scope, remarks, deprecated), datum, ensemble, cs);
@@ -1622,7 +1631,7 @@ codes: for (int i=0; i<codes.length; i++) {
* @return the given datum as an ensemble if it should be considered as such, or {@code null} otherwise.
* @throws ClassCastException if at least one member is not an instance of the specified type.
*/
- private static <D extends Datum> DatumEnsemble<D> tryAsEnsemble(final D datum, final Class<D> memberType) {
+ private static <D extends Datum> DatumEnsemble<D> wasDatumEnsemble(final D datum, final Class<D> memberType) {
if (datum instanceof DatumEnsemble<?>) {
return DefaultDatumEnsemble.castOrCopy((DatumEnsemble<?>) datum).cast(memberType);
}
@@ -1656,17 +1665,18 @@ codes: for (int i=0; i<codes.length; i++) {
ArgumentChecks.ensureNonNull("code", code);
Datum returnValue = null;
try (ResultSet result = executeQuery("Datum", "DATUM_CODE", "DATUM_NAME",
- "SELECT DATUM_CODE," +
- " DATUM_NAME," +
- " DATUM_TYPE," +
- " ORIGIN_DESCRIPTION," +
- " REALIZATION_EPOCH," +
- " AREA_OF_USE_CODE," + // Deprecated since EPSG version 10 (always null)
- " DATUM_SCOPE," +
- " REMARKS," +
- " DEPRECATED," +
- " ELLIPSOID_CODE," + // Only for geodetic type
- " PRIME_MERIDIAN_CODE" + // Only for geodetic type
+ "SELECT DATUM_CODE," + // [ 1]
+ " DATUM_NAME," + // [ 2]
+ " DATUM_TYPE," + // [ 3]
+ " ORIGIN_DESCRIPTION," + // [ 4]
+ " REALIZATION_EPOCH," + // [ 5]
+ " AREA_OF_USE_CODE," + // [ 6] — Deprecated since EPSG version 10 (always null)
+ " DATUM_SCOPE," + // [ 7]
+ " REMARKS," + // [ 8]
+ " DEPRECATED," + // [ 9]
+ " ELLIPSOID_CODE," + // [10] — Only for geodetic type
+ " PRIME_MERIDIAN_CODE," + // [11] — Only for geodetic type
+ " REALIZATION_METHOD_CODE" + // [12] — Only for vertical type
" FROM \"Datum\"" +
" WHERE DATUM_CODE = ?", code))
{
@@ -1736,7 +1746,8 @@ codes: for (int i=0; i<codes.length; i++) {
break;
}
case "vertical": {
- datum = datumFactory.createVerticalDatum(properties, null);
+ final RealizationMethod method = getRealizationMethod(getOptionalInteger(result, 12));
+ datum = datumFactory.createVerticalDatum(properties, method);
break;
}
/*
@@ -2459,6 +2470,34 @@ codes: for (int i=0; i<codes.length; i++) {
return returnValue;
}
+ /**
+ * Returns the realization method for the specified code.
+ *
+ * @param code code of the realization method, or {@code null} if none.
+ * @return realization method, or {@code null} if the given code was null.
+ */
+ private RealizationMethod getRealizationMethod(final Integer code) throws FactoryException, SQLException {
+ assert Thread.holdsLock(this);
+ RealizationMethod returnValue = realizationMethods.get(code);
+ if (returnValue == null && code != null) {
+ try (ResultSet result = executeQuery("DatumRealizationMethod",
+ "SELECT REALIZATION_METHOD_NAME" +
+ " FROM \"DatumRealizationMethod\"" +
+ " WHERE REALIZATION_METHOD_CODE = ?", code))
+ {
+ while (result.next()) {
+ final String name = getString(code, result, 1);
+ returnValue = ensureSingleton(VerticalDatumTypes.fromMethod(name), returnValue, code);
+ }
+ }
+ if (returnValue == null) {
+ throw noSuchAuthorityCode(RealizationMethod.class, String.valueOf(code));
+ }
+ realizationMethods.put(code, returnValue);
+ }
+ return returnValue;
+ }
+
/**
* Creates an unit of measurement from a code.
* Current implementation first checks if {@link Units#valueOfEPSG(int)} can provide a hard-coded unit
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index d3c836baf9..83a6998c9d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -366,9 +366,11 @@ public class SQLTranslator implements Function<String,String> {
/*
* A column named "BASE_CRS_CODE" in EPSG version 9 has been renamed "SOURCE_GEOGCRS_CODE" in version 10.
* Detects which name is used, with precedence to latest database version if the two columns are found.
+ * Opportunistically use this detection for generating empty columns for fields that did not existed in
+ * the "Datum" table of EPSG version 9.
*/
+ boolean isOldSchema = false;
skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTableName("Coordinate Reference System"), baseCRS)) {
- boolean isOldSchema = false;
while (result.next()) {
final String column = result.getString(Reflection.COLUMN_NAME);
if ("BASE_CRS_CODE".equalsIgnoreCase(column)) {
@@ -378,11 +380,12 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
isOldSchema = "SOURCE_GEOGCRS_CODE".equalsIgnoreCase(column);
}
}
- if (isOldSchema) {
- columnRenaming = new HashMap<>(columnRenaming);
- columnRenaming.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE");
- columnRenaming = Map.copyOf(columnRenaming);
- }
+ }
+ if (isOldSchema) {
+ columnRenaming = new HashMap<>(columnRenaming);
+ columnRenaming.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE"); // In table "Coordinate Reference System".
+ addMissingColumn("REALIZATION_METHOD_CODE", "INTEGER"); // In table "Datum".
+ columnRenaming = Map.copyOf(columnRenaming);
}
/*
* Detect if the database uses boolean types where applicable.
@@ -420,6 +423,15 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
&& !useBoolean;
}
+ /**
+ * Declares that the column of the given name shall be replaced by a placeholder.
+ * This method is invoked for columns added in <abbr>EPSG</abbr> version 10 or later
+ * when we detected that the schema in use is <abbr>EPSG</abbr> version 8 or before;
+ */
+ private void addMissingColumn(final String column, final String type) {
+ columnRenaming.put(column, "CAST(NULL AS " + type + ") AS " + column);
+ }
+
/**
* Returns the catalog that contains the EPSG schema. This is the catalog specified at construction time
* if it was non-null, or the catalog discovered by the constructor otherwise.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
index 5be71863b9..de6020319d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
@@ -35,8 +35,6 @@ import org.apache.sis.util.privy.Constants;
* This is a temporary object used for Well-Known Text formatting.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
- * @since 1.5
*/
public final class Epoch extends FormattableObject {
/**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index 58a22e5fb3..ecea0315c6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
@@ -24,6 +24,7 @@ import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.AxisDirection;
import org.apache.sis.util.Characters;
import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.privy.CodeLists;
import org.apache.sis.measure.Units;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -31,11 +32,15 @@ import org.opengis.referencing.datum.RealizationMethod;
/**
- * Extensions to the standard set of {@link RealizationEpoch}.
+ * Extensions to the standard set of {@link RealizationMethod}.
* Some of those constants are derived from a legacy {@code VerticalDatumType} code list.
* Those constants are not in public API because they were intentionally omitted from ISO 19111,
* and the ISO experts said that they should really not be public.
*
+ * <h2>Note on class naming</h2>
+ * {@code RealizationMethods} could have been a more appropriate name.
+ * For now we keep {@code VerticalDatumTypes} as a way to remind that this is currently only about vertical datum.
+ *
* @author Martin Desruisseaux (IRD, Geomatys)
*/
public final class VerticalDatumTypes {
@@ -73,6 +78,11 @@ public final class VerticalDatumTypes {
*/
static final String BAROMETRIC = "BAROMETRIC";
+ /**
+ * A value used in the <abbr>EPSG</abbr> database.
+ */
+ static final String LOCAL = "LOCAL";
+
/**
* Do not allow instantiation of this class.
*/
@@ -113,7 +123,7 @@ public final class VerticalDatumTypes {
* @param code the legacy vertical datum code.
* @return the vertical datum type, or {@code null} if none.
*/
- public static RealizationMethod fromLegacy(final int code) {
+ public static RealizationMethod fromLegacyCode(final int code) {
switch (code) {
// case 2000: return null; // CS_VD_Other
case 2001: return RealizationMethod.valueOf(ORTHOMETRIC); // CS_VD_Orthometric
@@ -132,7 +142,7 @@ public final class VerticalDatumTypes {
* @param method the realization method, or {@code null} if unknown.
* @return the legacy code for the given datum type, or 0 if unknown.
*/
- public static int toLegacy(final RealizationMethod method) {
+ public static int toLegacyCode(final RealizationMethod method) {
if (method != null) {
switch (method.name().toUpperCase(Locale.US)) {
case ORTHOMETRIC: return 2001; // CS_VD_Orthometric
@@ -154,7 +164,7 @@ public final class VerticalDatumTypes {
* @param method the realization method, or {@code null}.
* @return the vertical datum type name (never null).
*/
- public static String toName(final RealizationMethod method) {
+ public static String toLegacyName(final RealizationMethod method) {
if (method == RealizationMethod.GEOID) return "geoidal";
if (method == RealizationMethod.TIDAL) return "depth";
if (method != null) {
@@ -170,15 +180,36 @@ public final class VerticalDatumTypes {
* @param type the vertical datum type, or {@code null}.
* @return the realization method, or {@code null} if none.
*/
- public static RealizationMethod fromName(final String type) {
- if ("GEOIDAL" .equalsIgnoreCase(type)) return RealizationMethod.GEOID;
- if ("DEPTH" .equalsIgnoreCase(type)) return RealizationMethod.TIDAL;
+ public static RealizationMethod fromLegacyName(final String type) {
+ if ("geoidal" .equalsIgnoreCase(type)) return RealizationMethod.GEOID;
+ if ("depth" .equalsIgnoreCase(type)) return RealizationMethod.TIDAL;
+ if (LOCAL .equalsIgnoreCase(type)) return RealizationMethod.valueOf(LOCAL);
if (BAROMETRIC .equalsIgnoreCase(type)) return RealizationMethod.valueOf(BAROMETRIC);
if (ORTHOMETRIC.equalsIgnoreCase(type)) return RealizationMethod.valueOf(ORTHOMETRIC);
if (ELLIPSOIDAL.equalsIgnoreCase(type)) return ellipsoidal();
return null;
}
+ /**
+ * Returns the realization method from heuristic rules applied on the name.
+ *
+ * @param name the realization method name, or {@code null}.
+ * @return the realization method, or {@code null} if the given name was null.
+ */
+ public static RealizationMethod fromMethod(final String name) {
+ RealizationMethod method = fromLegacyName(name);
+ if (method == null && name != null && !name.isBlank()) {
+ final int s = name.lastIndexOf('-');
+ if (s >= 0 && name.substring(s+1).strip().equalsIgnoreCase("based")) {
+ method = CodeLists.forCodeName(RealizationMethod.class, name.substring(0, s));
+ }
+ if (method == null) {
+ method = RealizationMethod.valueOf(name);
+ }
+ }
+ return method;
+ }
+
/**
* Guesses the realization method of a datum from its name, aliases or a given vertical axis.
* This is sometimes needed after XML unmarshalling or WKT parsing, because GML 3.2 and ISO 19162
@@ -191,16 +222,16 @@ public final class VerticalDatumTypes {
* @param axis the vertical axis for which to guess a type, or {@code null} if unknown.
* @return a datum type, or {@code null} if none can be guessed.
*/
- public static RealizationMethod guess(final String name, final Collection<? extends GenericName> aliases,
+ public static RealizationMethod fromDatum(final String name, final Collection<? extends GenericName> aliases,
final CoordinateSystemAxis axis)
{
- RealizationMethod method = guess(name);
+ RealizationMethod method = fromDatum(name);
if (method != null) {
return method;
}
if (aliases != null) {
for (final GenericName alias : aliases) {
- method = guess(alias.tip().toString());
+ method = fromDatum(alias.tip().toString());
if (method != null) {
return method;
}
@@ -230,13 +261,13 @@ public final class VerticalDatumTypes {
}
/**
- * Guesses the realization method of a datum of the given name. This method attempts to guess only if
- * the given name contains at least one letter. If the type cannot be determined, returns {@code null}.
+ * Guesses the realization method of a datum of the given name.
+ * If the realization method cannot be determined, returns {@code null}.
*
* @param name name of the datum for which to guess a realization method, or {@code null}.
* @return a realization method, or {@code null} if none can be guessed.
*/
- private static RealizationMethod guess(final String name) {
+ private static RealizationMethod fromDatum(final String name) {
if (name != null) {
if (CharSequences.equalsFiltered("Mean Sea Level", name, Characters.Filter.LETTERS_AND_DIGITS, true)) {
return RealizationMethod.TIDAL;
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
index 28f4e0ca9f..b5fe441aec 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java
@@ -33,7 +33,6 @@ import org.opengis.referencing.datum.RealizationMethod;
*
* @author Martin Desruisseaux (Geomatys)
*/
-@SuppressWarnings("deprecation")
public final class VerticalDatumTypesTest extends TestCase {
/**
* Creates a new test case.
@@ -53,23 +52,35 @@ public final class VerticalDatumTypesTest extends TestCase {
}
/**
- * Tests the {@link VerticalDatumTypes#fromLegacy(int)} method.
+ * Tests the {@link VerticalDatumTypes#fromLegacyCode(int)} method.
*/
@Test
- public void testFromLegacy() {
- assertEquals(VerticalDatumTypes.ellipsoidal(), VerticalDatumTypes.fromLegacy(2002));
- assertEquals(RealizationMethod .GEOID, VerticalDatumTypes.fromLegacy(2005));
- assertEquals(RealizationMethod .TIDAL, VerticalDatumTypes.fromLegacy(2006));
+ public void testFromLegacyCode() {
+ assertEquals(VerticalDatumTypes.ellipsoidal(), VerticalDatumTypes.fromLegacyCode(2002));
+ assertEquals(RealizationMethod .GEOID, VerticalDatumTypes.fromLegacyCode(2005));
+ assertEquals(RealizationMethod .TIDAL, VerticalDatumTypes.fromLegacyCode(2006));
}
/**
- * Tests the {@link VerticalDatumTypes#toLegacy(RealizationMethod)} method.
+ * Tests the {@link VerticalDatumTypes#toLegacyCode(RealizationMethod)} method.
*/
@Test
- public void testToLegacy() {
- assertEquals(2002, VerticalDatumTypes.toLegacy(VerticalDatumTypes.ellipsoidal()));
- assertEquals(2005, VerticalDatumTypes.toLegacy(RealizationMethod .GEOID));
- assertEquals(2006, VerticalDatumTypes.toLegacy(RealizationMethod .TIDAL));
+ public void testToLegacyCode() {
+ assertEquals(2002, VerticalDatumTypes.toLegacyCode(VerticalDatumTypes.ellipsoidal()));
+ assertEquals(2005, VerticalDatumTypes.toLegacyCode(RealizationMethod .GEOID));
+ assertEquals(2006, VerticalDatumTypes.toLegacyCode(RealizationMethod .TIDAL));
+ }
+
+ /**
+ * Tests the {@link VerticalDatumTypes#fromMethod(String)} method
+ * with names from the <abbr>EPSG</abbr> database.
+ */
+ @Test
+ public void testFromMethod() {
+ assertEquals(RealizationMethod .LEVELLING, VerticalDatumTypes.fromMethod("Levelling-based"));
+ assertEquals(RealizationMethod .GEOID, VerticalDatumTypes.fromMethod("Geoid-based"));
+ assertEquals(RealizationMethod .TIDAL, VerticalDatumTypes.fromMethod("Tidal"));
+ assertEquals(VerticalDatumTypes.LOCAL, VerticalDatumTypes.fromMethod("Local").name());
}
/**
(sis) 02/04: Support the ANCHOR_EPOCH and FRAME_REFERENCE_EPOCH columns of the "Datum" table in EPSG version 10+.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 155af1559e513bfcff6d4fe35ce7c590caf6b6ce
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Jul 24 15:45:59 2025 +0200
Support the ANCHOR_EPOCH and FRAME_REFERENCE_EPOCH columns of the "Datum" table in EPSG version 10+.
---
.../org/apache/sis/temporal/LenientDateFormat.java | 4 +-
.../apache/sis/temporal/LenientDateFormatTest.java | 4 +
.../referencing/datum/DefaultGeodeticDatum.java | 2 +-
.../referencing/datum/DefaultVerticalDatum.java | 2 +-
.../referencing/factory/sql/EPSGDataAccess.java | 159 +++++++++++----------
.../sis/referencing/factory/sql/SQLTranslator.java | 7 +-
6 files changed, 100 insertions(+), 78 deletions(-)
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
index 337b5762c0..4649c7c3be 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
@@ -31,6 +31,8 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
+import java.time.YearMonth;
+import java.time.Year;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.temporal.Temporal;
@@ -103,7 +105,7 @@ public final class LenientDateFormat extends DateFormat {
* @see #parseInstantUTC(CharSequence, int, int)
*/
private static TemporalQuery<?>[] QUERIES = {
- Instant::from, LocalDateTime::from, LocalDate::from
+ Instant::from, LocalDateTime::from, LocalDate::from, YearMonth::from, Year::from
};
/**
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/LenientDateFormatTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/LenientDateFormatTest.java
index 492d98f7d2..3328d05e86 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/LenientDateFormatTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/LenientDateFormatTest.java
@@ -19,6 +19,8 @@ package org.apache.sis.temporal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.Year;
import java.util.Date;
import java.text.ParseException;
@@ -99,6 +101,8 @@ public final class LenientDateFormatTest extends TestCase {
assertEquals(LocalDateTime.of(2016, 6, 27, 16, 48), LenientDateFormat.parseBest("2016-06-27T16:48"));
assertEquals(LocalDateTime.of(2016, 6, 27, 16, 48), LenientDateFormat.parseBest("2016-06-27 16:48"));
assertEquals(LocalDate.of(2016, 6, 27), LenientDateFormat.parseBest("2016-06-27"));
+ assertEquals(YearMonth.of(2016, 6), LenientDateFormat.parseBest("2016-06"));
+ assertEquals(Year.of(2016), LenientDateFormat.parseBest("2016"));
}
/**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index 75cc8178e2..736473a79e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@ -586,7 +586,7 @@ public class DefaultGeodeticDatum extends AbstractDatum implements GeodeticDatum
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
- return super.equals(object) && (mode != ComparisonMode.STRICT ||
+ return super.equals(object, mode) && (mode != ComparisonMode.STRICT ||
frameReferenceEpoch.equals(((Dynamic) object).frameReferenceEpoch));
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 4a097c0eed..0d27307af2 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@ -283,7 +283,7 @@ public class DefaultVerticalDatum extends AbstractDatum implements VerticalDatum
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
- return super.equals(object) && (mode != ComparisonMode.STRICT ||
+ return super.equals(object, mode) && (mode != ComparisonMode.STRICT ||
frameReferenceEpoch.equals(((Dynamic) object).frameReferenceEpoch));
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index e2cef4ba6a..71746b6337 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -24,10 +24,8 @@ import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.Iterator;
-import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
@@ -43,6 +41,7 @@ import java.sql.SQLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDate;
+import java.time.Year;
import java.time.temporal.Temporal;
import javax.measure.Unit;
import javax.measure.quantity.Angle;
@@ -227,15 +226,6 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
*/
private String lastTableForName;
- /**
- * The calendar instance for creating {@link Date} objects from a year (the "epoch" in datum definition).
- * We use the UTC timezone, which may not be quite accurate. But there is no obvious timezone for "epoch",
- * and the "epoch" is an approximation anyway.
- *
- * @see #getCalendar()
- */
- private Calendar calendar;
-
/**
* A pool of prepared statements. Keys are {@link String} objects related to their originating method
* (for example "Ellipsoid" for {@link #createEllipsoid(String)}).
@@ -384,19 +374,6 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
return owner.getLocale();
}
- /**
- * Returns the calendar to use for reading dates in the database.
- */
- @SuppressWarnings("ReturnOfDateField")
- private Calendar getCalendar() {
- if (calendar == null) {
- calendar = Calendar.getInstance(TimeZone.getTimeZone(Constants.UTC), Locale.CANADA);
- // Canada locale is closer to ISO than US.
- }
- calendar.clear();
- return calendar;
- }
-
/**
* Returns the authority for this EPSG dataset. The returned citation contains the database version
* in the {@linkplain Citation#getEdition() edition} attribute, together with date of last update in
@@ -857,6 +834,54 @@ codes: for (int i=0; i<codes.length; i++) {
return stmt;
}
+ /**
+ * Gets the local date or year from the specified {@link ResultSet}, or {@code null} if none.
+ * This method parses the date itself from the character string because the column type is sometime declared
+ * as {@code VARCHAR} instead of {@code DATE} in the <abbr>SQL</abbr> scripts distributed by <abbr>EPSG</abbr>.
+ * The Apache <abbr>SIS</abbr> installer replaces {@code VARCHAR} by {@code DATE}, but we have no guarantee
+ * that we are reading an <abbr>EPSG</abbr> database created by our installer. Furthermore, an older version
+ * of <abbr>EPSG</abbr> installer was using {@code SMALLINT} instead of {@code DATE},
+ * because scripts before <abbr>EPSG</abbr> 9.0 were reporting only the epoch year.
+ *
+ * @param result the result set to fetch value from.
+ * @param columnIndex the column index (1-based).
+ * @param caller the caller, used for reporting a warning in case of parsing error.
+ * @return the temporal at the specified column, or {@code null}.
+ * @throws SQLException if an error occurred while querying the database.
+ */
+ private static Temporal getOptionalTemporal(final ResultSet result, final int columnIndex, final String caller)
+ throws SQLException
+ {
+ try {
+ return LenientDateFormat.parseBest(getOptionalString(result, columnIndex));
+ } catch (NumberFormatException exception) {
+ unexpectedException(caller, exception); // Not a fatal error.
+ }
+ return null;
+ }
+
+ /**
+ * Gets the epoch from the specified {@link ResultSet}, or {@code null} if none.
+ * The column type should be a floating point number.
+ *
+ * @param result the result set to fetch value from.
+ * @param columnIndex the column index (1-based).
+ * @return the epoch at the specified column, or {@code null}.
+ * @throws SQLException if an error occurred while querying the database.
+ */
+ private static Temporal getOptionalEpoch(final ResultSet result, final int columnIndex) throws SQLException {
+ final double epoch = getOptionalDouble(result, columnIndex);
+ if (Double.isNaN(epoch)) {
+ return null;
+ }
+ final var year = Year.of((int) epoch);
+ final long day = Math.round((epoch - year.getValue()) * year.length());
+ if (day == 0) {
+ return year;
+ }
+ return year.atMonth(year.atDay(Math.toIntExact(day)).getMonth());
+ }
+
/**
* Gets the value from the specified {@link ResultSet}, or {@code null} if none.
*
@@ -1669,58 +1694,37 @@ codes: for (int i=0; i<codes.length; i++) {
" DATUM_NAME," + // [ 2]
" DATUM_TYPE," + // [ 3]
" ORIGIN_DESCRIPTION," + // [ 4]
- " REALIZATION_EPOCH," + // [ 5]
- " AREA_OF_USE_CODE," + // [ 6] — Deprecated since EPSG version 10 (always null)
- " DATUM_SCOPE," + // [ 7]
- " REMARKS," + // [ 8]
- " DEPRECATED," + // [ 9]
- " ELLIPSOID_CODE," + // [10] — Only for geodetic type
- " PRIME_MERIDIAN_CODE," + // [11] — Only for geodetic type
- " REALIZATION_METHOD_CODE" + // [12] — Only for vertical type
+ " ANCHOR_EPOCH," + // [ 5]
+ " FRAME_REFERENCE_EPOCH," + // [ 6] — NULL for static datum, non-null if dynamic.
+ " PUBLICATION_DATE," + // [ 7] — Was REALIZATION_EPOCH in EPSG version 9.
+ " AREA_OF_USE_CODE," + // [ 8] — Deprecated since EPSG version 10 (always null)
+ " DATUM_SCOPE," + // [ 9]
+ " REMARKS," + // [10]
+ " DEPRECATED," + // [11]
+ " ELLIPSOID_CODE," + // [12] — Only for geodetic type
+ " PRIME_MERIDIAN_CODE," + // [13] — Only for geodetic type
+ " REALIZATION_METHOD_CODE" + // [14] — Only for vertical type
" FROM \"Datum\"" +
" WHERE DATUM_CODE = ?", code))
{
while (result.next()) {
- final Integer epsg = getInteger (code, result, 1);
- final String name = getString (code, result, 2);
- final String type = getString (code, result, 3);
- final String anchor = getOptionalString (result, 4);
- final String epoch = getOptionalString (result, 5);
- final String area = getOptionalString (result, 6);
- final String scope = getOptionalString (result, 7);
- final String remarks = getOptionalString (result, 8);
- final boolean deprecated = getOptionalBoolean(result, 9);
+ final Integer epsg = getInteger (code, result, 1);
+ final String name = getString (code, result, 2);
+ final String type = getString (code, result, 3);
+ final String anchor = getOptionalString (result, 4);
+ final Temporal epoch = getOptionalEpoch (result, 5);
+ final Temporal dynamic = getOptionalEpoch (result, 6);
+ final Temporal publish = getOptionalTemporal(result, 7, "createDatum");
+ final String area = getOptionalString (result, 8);
+ final String scope = getOptionalString (result, 9);
+ final String remarks = getOptionalString (result, 10);
+ final boolean deprecated = getOptionalBoolean (result, 11);
@SuppressWarnings("LocalVariableHidesMemberVariable")
Map<String,Object> properties = createProperties("Datum",
epsg, name, null, area, scope, remarks, deprecated);
properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
- if (epoch != null) try {
- /*
- * Parse the date manually because it is declared as a VARCHAR instead of DATE in original
- * SQL scripts. Apache SIS installer replaces VARCHAR by DATE, but we have no guarantee that
- * we are reading an EPSG database created by our installer. Furthermore, an older version of
- * EPSG installer was using SMALLINT instead of DATE, because scripts before EPSG 9.0 were
- * reporting only the epoch year.
- */
- final CharSequence[] fields = CharSequences.split(epoch, '-');
- int year = 0, month = 0, day = 1;
- for (int i = Math.min(fields.length, 3); --i >= 0;) {
- final int f = Integer.parseInt(fields[i].toString());
- switch (i) {
- case 0: year = f; break;
- case 1: month = f-1; break;
- case 2: day = f; break;
- }
- }
- if (year != 0) {
- @SuppressWarnings("LocalVariableHidesMemberVariable")
- final Calendar calendar = getCalendar();
- calendar.set(year, month, day);
- properties.put(Datum.ANCHOR_EPOCH_KEY, calendar.getTime().toInstant());
- }
- } catch (NumberFormatException exception) {
- unexpectedException("createDatum", exception); // Not a fatal error.
- }
+ properties.put(Datum.ANCHOR_EPOCH_KEY, epoch);
+ properties.put(Datum.PUBLICATION_DATE_KEY, publish);
/*
* The following switch statement should have a case for all "epsg_datum_kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in this Java code are in lower cases.
@@ -1736,18 +1740,26 @@ codes: for (int i=0; i<codes.length; i++) {
case "dynamic geodetic":
case "geodetic": {
properties = new HashMap<>(properties); // Protect from changes
- final Ellipsoid ellipsoid = owner.createEllipsoid (getString(code, result, 10));
- final PrimeMeridian meridian = owner.createPrimeMeridian(getString(code, result, 11));
+ final Ellipsoid ellipsoid = owner.createEllipsoid (getString(code, result, 12));
+ final PrimeMeridian meridian = owner.createPrimeMeridian(getString(code, result, 13));
final BursaWolfParameters[] param = createBursaWolfParameters(meridian, epsg);
if (param != null) {
properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, param);
}
- datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+ if (dynamic != null) {
+ datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian, dynamic);
+ } else {
+ datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian);
+ }
break;
}
case "vertical": {
- final RealizationMethod method = getRealizationMethod(getOptionalInteger(result, 12));
- datum = datumFactory.createVerticalDatum(properties, method);
+ final RealizationMethod method = getRealizationMethod(getOptionalInteger(result, 14));
+ if (dynamic != null) {
+ datum = datumFactory.createVerticalDatum(properties, method, dynamic);
+ } else {
+ datum = datumFactory.createVerticalDatum(properties, method);
+ }
break;
}
/*
@@ -1870,6 +1882,7 @@ codes: for (int i=0; i<codes.length; i++) {
" AREA_OF_USE_CODE" + // Deprecated since EPSG version 10 (always null).
" FROM \"Coordinate_Operation\"" +
" WHERE DEPRECATED=0" + // Do not put spaces around "=" - SQLTranslator searches for this exact match.
+ " AND AREA_OF_USE_CODE IS NOT NULL" +
" AND TARGET_CRS_CODE = " + BursaWolfInfo.TARGET_CRS +
" AND COORD_OP_METHOD_CODE >= " + BursaWolfInfo.MIN_METHOD_CODE +
" AND COORD_OP_METHOD_CODE <= " + BursaWolfInfo.MAX_METHOD_CODE +
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index 83a6998c9d..cd160a4feb 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -383,8 +383,11 @@ skip: try (ResultSet result = md.getColumns(catalog, schemaPattern, toActualTa
}
if (isOldSchema) {
columnRenaming = new HashMap<>(columnRenaming);
- columnRenaming.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE"); // In table "Coordinate Reference System".
- addMissingColumn("REALIZATION_METHOD_CODE", "INTEGER"); // In table "Datum".
+ columnRenaming.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE"); // In table "Coordinate Reference System".
+ columnRenaming.put("PUBLICATION_DATE", "REALIZATION_EPOCH"); // In table "Datum".
+ addMissingColumn ("ANCHOR_EPOCH", "DOUBLE PRECISION"); // In table "Datum".
+ addMissingColumn ("FRAME_REFERENCE_EPOCH", "DOUBLE PRECISION"); // In table "Datum".
+ addMissingColumn ("REALIZATION_METHOD_CODE", "INTEGER"); // In table "Datum".
columnRenaming = Map.copyOf(columnRenaming);
}
/*