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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 96061e870a `TableInfo` needs to support the new types used in EPSG
version 10+. In particular, we use `DynamicReferenceFrance` as a mixin
interface. Also be more precise in the number of dimensions of geographic CRS.
96061e870a is described below
commit 96061e870a96bbff8a85c3969ba291ff2ed51487
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Aug 30 23:58:42 2025 +0200
`TableInfo` needs to support the new types used in EPSG version 10+.
In particular, we use `DynamicReferenceFrance` as a mixin interface.
Also be more precise in the number of dimensions of geographic CRS.
---
.../sis/metadata/privy/NameToIdentifier.java | 2 +-
.../apache/sis/referencing/IdentifiedObjects.java | 3 +-
.../sis/referencing/datum/DatumOrEnsemble.java | 17 +-
.../referencing/factory/sql/AuthorityCodes.java | 27 +--
.../referencing/factory/sql/EPSGCodeFinder.java | 26 ++-
.../referencing/factory/sql/EPSGDataAccess.java | 66 ++++----
.../sis/referencing/factory/sql/EPSGInstaller.java | 9 +-
.../sis/referencing/factory/sql/SQLTranslator.java | 11 --
.../sis/referencing/factory/sql/TableInfo.java | 182 +++++++++++++++++----
.../referencing/factory/sql/EPSGFactoryTest.java | 23 ++-
.../referencing/factory/sql/epsg/DebugTools.sql | 10 ++
11 files changed, 252 insertions(+), 124 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
index 6e154515c4..678b159013 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/privy/NameToIdentifier.java
@@ -230,7 +230,7 @@ public final class NameToIdentifier implements Identifier {
* @return {@code true} if the primary name or at least one alias matches
the given {@code name}.
*/
public static boolean isHeuristicMatchForName(final Identifier name, final
Collection<GenericName> aliases,
- CharSequence toSearch, final Simplifier simplifier)
+ CharSequence toSearch, final
Simplifier simplifier)
{
if (toSearch != null) {
CharSequence code = (name != null) ? name.getCode() : null;
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index 9822787bbf..67e0e6a6ae 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@ -768,7 +768,8 @@ public final class IdentifiedObjects extends Static {
*/
return ((AbstractIdentifiedObject)
object).isHeuristicMatchForName(name);
} else {
- return NameToIdentifier.isHeuristicMatchForName(object.getName(),
object.getAlias(), name,
+ return NameToIdentifier.isHeuristicMatchForName(
+ object.getName(), object.getAlias(), name,
NameToIdentifier.Simplifier.DEFAULT);
}
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
index b6a813a112..d08ce7f653 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java
@@ -427,18 +427,27 @@ public final class DatumOrEnsemble extends Static {
if (match != null) {
return match;
}
- String name = ensemble.getName().getCode();
- if (IdentifiedObjects.isHeuristicMatchForName(datum, name)) {
+ /*
+ * We could not answer the question using identifiers. Try using the
names.
+ * The primary name is likely to not match, because ensemble names in
EPSG
+ * dataset often ends with "ensemble" while datum names often do not.
But
+ * we are more interrested in the ensemble's aliases in the next line.
+ */
+ if (IdentifiedObjects.isHeuristicMatchForName(ensemble,
datum.getName().getCode())) {
return true;
}
+ /*
+ * Try to remove the "ensemble" prefix in the datum ensemble name and
try again.
+ * This time, the comparison will also check `datum` aliases instead
of `ensemble`.
+ */
+ String name = ensemble.getName().getCode();
if (name.endsWith(ENSEMBLE)) {
int i = name.length() - ENSEMBLE.length();
if (i > (i = CharSequences.skipTrailingWhitespaces(name, 0, i))) {
name = name.substring(0, i); // Remove the "ensemble"
suffix.
- return IdentifiedObjects.isHeuristicMatchForName(datum, name);
}
}
- return false;
+ return IdentifiedObjects.isHeuristicMatchForName(datum, name);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
index afa766de0e..5328065288 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
@@ -24,6 +24,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.Statement;
+import org.opengis.referencing.IdentifiedObject;
import org.apache.sis.metadata.sql.privy.SQLUtilities;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.collection.IntegerList;
@@ -79,10 +80,10 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
private final transient EPSGDataAccess factory;
/**
- * The interface of referencing objects for which this map contains the
code.
- * May be a super-interface of the type specified to the constructor.
+ * The key to use for caching this set of authority codes.
+ * May be a generalization of the key given at construction time.
*/
- final Class<?> type;
+ final Object cacheKey;
/**
* The SQL commands that this {@code AuthorityCodes} may need to execute.
@@ -122,13 +123,20 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
private transient IntegerList codes;
/**
- * Creates a new map of authority codes for the specified type.
+ * Creates a new map of authority codes for the specified object instance
of class.
+ * The {@code object} argument shall be one of the following types:
+ *
+ * <ul>
+ * <li>An {@link IdentifiedObject} instance.</li>
+ * <li>The {@link Class} of an {@code IdentifiedObject}. It may be an
implementation class.</li>
+ * <li>An opaque key computed by {@link
TableInfo#toCacheKey(IdentifiedObject)} (useful for caching).</li>
+ * </ul>
*
* @param table the table to query.
- * @param type the type to query.
+ * @param object an {@link IdentifiedObject}, a {@code Class} or an
opaque cache key.
* @param factory the factory originator.
*/
- AuthorityCodes(final TableInfo table, final Class<?> type, final
EPSGDataAccess factory) throws SQLException {
+ AuthorityCodes(final TableInfo table, final Object object, final
EPSGDataAccess factory) throws SQLException {
this.factory = factory;
sql = new String[NUM_QUERIES];
statements = new Statement[NUM_QUERIES];
@@ -141,7 +149,7 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
final int columnNameStart = buffer.append("SELECT ").length();
final int columnNameEnd = buffer.append(table.codeColumn).length();
buffer.append(" FROM ").append(table.fromClause);
- this.type = table.where(factory, type, buffer);
+ cacheKey = table.appendWhere(factory, object, buffer);
final int conditionStart = buffer.length();
if (table.showColumn != null) {
buffer.append(table.showColumn).append("=TRUE AND ");
@@ -311,8 +319,7 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
/**
* Returns the object name associated to the given authority code, or
{@code null} if none.
- * If there is no name for the {@linkplain #type} of object being queried,
then this method
- * returns {@code null}.
+ * If there is no name for the object being queried, then this method
returns {@code null}.
*
* @param code the code for which to get the description. May be a
string or an integer.
* @return the description for the given code, or {@code null} if none.
@@ -399,7 +406,7 @@ final class AuthorityCodes extends
AbstractMap<String,String> implements Seriali
size = "size" + (results != null ? " ≥ " : " = ") +
codes.size();
}
}
- return Strings.toString(getClass(), "type", type.getSimpleName(),
null, size);
+ return Strings.toString(getClass(), "cacheKey", cacheKey, null, size);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index ebe9fe21ef..5c95acd0bc 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -158,16 +158,16 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder
{
final Class<T> type, final T dependency, final boolean ignoreAxes)
throws FactoryException
{
if (dependency != null) try {
- final Class<? extends IdentifiedObject> pt = declaredType;
- final boolean previous = isIgnoringAxes();
+ final Class<? extends IdentifiedObject> previousType =
declaredType;
+ final boolean previousAxes = isIgnoringAxes();
final Set<IdentifiedObject> find;
try {
- setIgnoringAxes(ignoreAxes | previous);
+ setIgnoringAxes(ignoreAxes | previousAxes);
declaredType = type;
find = find(dependency);
} finally {
- declaredType = pt;
- setIgnoringAxes(previous);
+ declaredType = previousType;
+ setIgnoringAxes(previousAxes);
}
final Set<Number> filters = JDK19.newLinkedHashSet(find.size());
for (final IdentifiedObject dep : find) {
@@ -416,7 +416,7 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
};
} else try {
// Not a supported type. Returns all codes if not too expensive.
- return dao.getAuthorityCodes(declaredType, addTo);
+ return dao.getAuthorityCodes(object, addTo);
} catch (SQLException exception) {
throw databaseFailure(exception);
}
@@ -462,7 +462,7 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
* It may be absent (typically, only datums or reference frames have
that condition).
*/
buffer.append("SELECT ").append(source.codeColumn).append(" FROM
").append(source.fromClause);
- source.where(dao, object, buffer); // Unconditionally append
a "WHERE" clause.
+ source.appendWhere(dao, object, buffer); // Unconditionally append
a "WHERE" clause.
boolean isNext = false;
for (final Condition filter : filters) {
isNext |= filter.appendToWhere(buffer, isNext);
@@ -638,9 +638,6 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
/** Snapshot of the search domain as it was at collection construction
time. */
private final Domain domain;
- /** Whether to try to easy search methods before the expansive method.
*/
- private final boolean optimize;
-
/** Sequential number of the algorithm used for filling the {@link
#codes} collection so far. */
private byte searchMethod;
@@ -658,8 +655,7 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
this.source = source;
this.domain = getSearchDomain();
this.codes = new LinkedHashSet<>();
- optimize = (domain != Domain.EXHAUSTIVE_VALID_DATASET);
- if (optimize) {
+ if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
for (final Identifier id : object.getIdentifiers()) {
if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace()))
try {
codes.add(Integer.valueOf(id.getCode()));
@@ -699,17 +695,17 @@ crs: if (isInstance(CoordinateReferenceSystem.class,
object)) {
do {
switch (searchMethod) {
case 0: { // Fetch codes from the name.
- if (optimize) {
+ if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
name = getName(object);
if (name != null) { // Should never be null,
but we are paranoiac.
namePattern = dao.toLikePattern(name);
- dao.findCodesFromName(source,
object.getClass(), namePattern, name, addTo);
+ dao.findCodesFromName(source,
TableInfo.toCacheKey(object), namePattern, name, addTo);
}
}
break;
}
case 1: { // Fetch codes from the aliases.
- if (optimize) {
+ if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
if (namePattern != null) {
dao.findCodesFromAlias(source, namePattern,
name, addTo);
}
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 f3e64af99e..d25d4afe10 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
@@ -241,7 +241,7 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* and returns {@code false} if some are found (thus blocking the call to
{@link #close()}
* by the {@link
org.apache.sis.referencing.factory.ConcurrentAuthorityFactory} timer).</p>
*/
- private final Map<Class<?>, CloseableReference> authorityCodes = new
HashMap<>();
+ private final Map<Object, CloseableReference> authorityCodes = new
HashMap<>();
/**
* Cache for axis names, conventional reference systems, realization
methods or naming systems.
@@ -561,19 +561,14 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* This method may do nothing if getting all codes would be too expensive
* (especially since the caller would instantiate all enumerated objects).
*
- * @param type the spatial reference objects type, or {@code null} if
unspecified.
- * @param addTo the collection where to add all codes.
+ * @param object the object to search in the database.
+ * @param addTo the collection where to add all codes.
* @return whether the collection has changed as a result of this method
call.
* @throws SQLException if an error occurred while querying the database.
*/
- final boolean getAuthorityCodes(final Class<? extends IdentifiedObject>
type, final Collection<Integer> addTo) throws SQLException {
- if (type != null) {
- final AuthorityCodes codes = getCodeMap(type, null, false);
- if (codes != null) {
- return codes.getAllCodes(addTo);
- }
- }
- return false;
+ final boolean getAuthorityCodes(final IdentifiedObject object, final
Collection<Integer> addTo) throws SQLException {
+ final AuthorityCodes codes = getCodeMap(TableInfo.toCacheKey(object),
null, false);
+ return (codes != null) && codes.getAllCodes(addTo);
}
/**
@@ -581,17 +576,19 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
* The cautions documented in {@link #getAuthorityCodes(Class)} apply also
to this map.
* If the given type is unsupported or too generic, returns {@code null}.
*
- * @param type the spatial reference objects type.
- * @param source the table from which to get the authority codes, or
{@code null} for automatic.
- * @param publish whether the returned authority codes will be given to
a user outside this package.
+ * @param cacheKey object class or {@link
TableInfo#toCacheKey(IdentifiedObject)} value.
+ * @param source the table from which to get the authority codes, or
{@code null} for automatic.
+ * @param publish whether the returned authority codes will be given to
a user outside this package.
* @return the map of authority codes associated to their names, or {@code
null} if unsupported.
* @throws FactoryException if access to the underlying database failed.
*
* @see #getAuthorityCodes(Class)
* @see #getDescriptionText(Class, String)
*/
- private synchronized AuthorityCodes getCodeMap(final Class<?> type,
TableInfo source, boolean publish) throws SQLException {
- CloseableReference reference = authorityCodes.get(type);
+ private synchronized AuthorityCodes getCodeMap(final Object cacheKey,
TableInfo source, boolean publish)
+ throws SQLException
+ {
+ CloseableReference reference = authorityCodes.get(cacheKey);
if (reference != null) {
AuthorityCodes existing = reference.get();
if (existing != null) {
@@ -599,26 +596,29 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
return existing;
}
}
- if (source == null) {
- for (TableInfo c : TableInfo.values()) {
- if (c.isSpecificEnough() && c.type.isAssignableFrom(type)) {
+ if (source != null) {
+ assert source.isSpecificEnough() &&
source.type.isAssignableFrom(TableInfo.typeOfCacheKey(cacheKey)) : source;
+ } else {
+ final Class<?> userType = TableInfo.typeOfCacheKey(cacheKey);
+ for (TableInfo candidate : TableInfo.values()) {
+ if (candidate.isSpecificEnough() &&
candidate.type.isAssignableFrom(userType)) {
if (source != null) {
return null; // The specified type is too
generic.
}
- source = c;
+ source = candidate;
}
}
if (source == null) {
return null; // The specified type is
unsupported.
}
}
- AuthorityCodes codes = new AuthorityCodes(source, type, this);
+ AuthorityCodes codes = new AuthorityCodes(source, cacheKey, this);
/*
* Maybe an instance already existed but was not found above because
the user specified some
* implementation class instead of an interface class. Before to
return a newly created map,
* check again in the cached maps using the type computed by
AuthorityCodes itself.
*/
- reference = authorityCodes.get(codes.type);
+ reference = authorityCodes.get(codes.cacheKey);
if (reference != null) {
AuthorityCodes existing = reference.get();
if (existing != null) {
@@ -629,10 +629,10 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
}
if (reference == null) {
reference = codes.createReference();
- authorityCodes.put(codes.type, reference);
+ authorityCodes.put(codes.cacheKey, reference);
}
- if (type != codes.type) {
- authorityCodes.put(type, reference);
+ if (cacheKey != codes.cacheKey) {
+ authorityCodes.put(cacheKey, reference);
}
reference.published |= publish;
return codes;
@@ -780,17 +780,17 @@ public class EPSGDataAccess extends
GeodeticAuthorityFactory implements CRSAutho
/**
* Finds the authority codes for the given name.
*
- * @param source information about the table where the code should
appear.
- * @param type the type of object to search. Should be assignable to
{@code source.type}.
- * @param pattern the name to search as a pattern that can be used with
{@code LIKE}.
- * @param name the original name. This is a temporary workaround for
a Derby bug (see {@code filterFalsePositive(…)}).
- * @param addTo the collection where to add the codes that have been
found.
+ * @param source information about the table where the code should
appear.
+ * @param cacheKey object class or {@link
TableInfo#toCacheKey(IdentifiedObject)} value.
+ * @param pattern the name to search as a pattern that can be used with
{@code LIKE}.
+ * @param name the original name. This is a temporary workaround for
a Derby bug (see {@code filterFalsePositive(…)}).
+ * @param addTo the collection where to add the codes that have been
found.
* @throws SQLException if an error occurred while querying the database.
*/
- final void findCodesFromName(final TableInfo source, final Class<?> type,
final String pattern, final String name, final Collection<Integer> addTo)
- throws SQLException
+ final void findCodesFromName(final TableInfo source, final Object
cacheKey, final String pattern, final String name,
+ final Collection<Integer> addTo) throws
SQLException
{
- AuthorityCodes codes = getCodeMap(type, source, false);
+ AuthorityCodes codes = getCodeMap(cacheKey, source, false);
if (codes != null) {
codes.findCodesFromName(pattern, name, addTo);
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
index 1b4deea485..82828676ab 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
@@ -74,17 +74,10 @@ final class EPSGInstaller extends ScriptRunner {
"CRS Kind", "VARCHAR(13)", // Original:
VARCHAR(24) for column "coord_ref_sys_kind".
"CS Kind", "VARCHAR(15)", // Original:
VARCHAR(24) for column "coord_sys_type".
"Supersession Type", "VARCHAR(12)", // Original:
VARCHAR(50) for column "supersession_type".
- "Table Name", ENUM_REPLACEMENT); // Original:
VARCHAR(80) for columns "object_table_name".
+ "Table Name", "VARCHAR(36)"); // Original:
VARCHAR(80) for columns "object_table_name".
}
}
- /**
- * The <abbr>SQL</abbr> type to use as a replacement for enumerated values
in databases that do not
- * support enumerations. The maximal length declared in this constant
should be the greatest length
- * declared in {@code VARCHAR(…)} substitutions done when {@link
#isEnumTypeSupported} is false.
- */
- static final String ENUM_REPLACEMENT = "VARCHAR(36)";
-
/**
* Invoked for each text found in a SQL statement. This method replaces
{@code ''} by {@code Null}.
* The intent is to consistently use the null value for meaning "no
information", which is not the
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 b275964bab..a7ec894923 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
@@ -231,8 +231,6 @@ public class SQLTranslator implements UnaryOperator<String>
{
* than character varying. In such case, this field contains the
enumeration type. If {@code null}, then
* then column type is {@code VARCHAR} and the cast can be omitted.
* If non-null, this string should contain the identifier quotes.
- *
- * @see #useEnumerations()
*/
private String tableNameEnum;
@@ -596,15 +594,6 @@ check: for (;;) {
return useBoolean;
}
- /**
- * Returns {@code true} if the database uses enumeration values where
applicable.
- * This method use the {@value #ENUMERATION_COLUMN} column as a sentinel
value for
- * detecting whether enumerations are used for the whole <abbr>EPSG</abbr>
database.
- */
- final boolean useEnumerations() {
- return tableNameEnum != null;
- }
-
/**
* Converts a mixed-case table name to the convention used in the database.
* The names of the tables for the two conventions are listed in a table
in the Javadoc of this class.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index b6d6726fa9..2622020443 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.referencing.factory.sql;
+import java.util.Map;
import javax.measure.Unit;
import org.opengis.metadata.extent.Extent;
import org.opengis.referencing.IdentifiedObject;
@@ -47,7 +48,7 @@ import org.apache.sis.referencing.crs.DefaultGeocentricCRS;
* As of ISO 19111:2019, we have no standard way to identify the geocentric
case from a {@link Class} argument
* because the standard does not provide the {@code GeocentricCRS} interface.
This implementation fallbacks on
* the <abbr>SIS</abbr>-specific geocentric <abbr>CRS</abbr> class. This
special case is implemented in the
- * {@link #where(EPSGDataAccess, IdentifiedObject, StringBuilder)} method.
+ * {@link #appendWhere(EPSGDataAccess, Object, StringBuilder)} method.
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
@@ -63,7 +64,7 @@ enum TableInfo {
new Class<?>[] { ProjectedCRS.class, GeographicCRS.class,
DefaultGeocentricCRS.class,
VerticalCRS.class, CompoundCRS.class,
EngineeringCRS.class,
DerivedCRS.class, TemporalCRS.class,
ParametricCRS.class}, // See comment below
- new String[] {"projected", "geographic",
"geocentric",
+ new String[] {"projected", "geographic 2D",
"geocentric", // 3D case handled below
"vertical", "compound",
"engineering",
"derived", "temporal",
"parametric"}, // See comment below
"SHOW_CRS", true),
@@ -82,9 +83,11 @@ enum TableInfo {
"DATUM_CODE",
"DATUM_NAME",
"DATUM_TYPE",
- new Class<?>[] { GeodeticDatum.class, VerticalDatum.class,
EngineeringDatum.class,
+ new Class<?>[] { DatumEnsemble.class, // Need to be first because
Apache SIS uses as mixin interface.
+ GeodeticDatum.class, VerticalDatum.class,
EngineeringDatum.class,
TemporalDatum.class, ParametricDatum.class},
- new String[] {"geodetic", "vertical",
"engineering",
+ new String[] {"ensemble",
+ "geodetic", "vertical",
"engineering",
"temporal", "parametric"}, //
Same comment as in the CRS case above.
null, true),
@@ -192,6 +195,28 @@ enum TableInfo {
"UNIT_OF_MEAS_NAME",
null, null, null, null, false);
+ /**
+ * Types to consider as synonymous for searching purposes. This map exists
for historical reasons,
+ * because dynamic datum and datum ensemble did not existed in older
<abbr>ISO</abbr> 19111 standards.
+ * If an object to search is "geodetic", there is a possibility that it is
defined in the old way and
+ * actually appears as a "dynamic geodetic" or "ensemble" in the
<abbr>EPSG</abbr> geodetic dataset.
+ *
+ * <p>The "geographic 3D" case is handled in a special way. It is
considered as a synonymous of
+ * "geographic 2D" only when we don't know the number of dimensions.</p>
+ */
+ private static final Map<String, String[]> SYNONYMOUS_TYPES = Map.of(
+ "geodetic", new String[] {"dynamic geodetic", "ensemble"},
+ "geographic 2D", new String[] {"geographic 3D"});
+
+ /**
+ * Types to replace by specialized types when the user-specified instance
implements a mixin interface.
+ * For example, {@link DynamicReferenceFrame} means to not search for any
geodetic datum, but only for
+ * dynamic geodetic datum.
+ */
+ private static final Map<String, String> DYNAMIC_TYPES = Map.of(
+ "geodetic", "dynamic geodetic");
+ // We would expect a "dynamic vertical" as well, but we don't see
it yet in EPSG database.
+
/**
* The class of object to be created (usually a GeoAPI interface).
*/
@@ -232,6 +257,7 @@ enum TableInfo {
/**
* Names of {@link #subTypes} in the database, or {@code null} if none.
+ * This array must have the same length as {@link #subTypes}.
*/
private final String[] typeNames;
@@ -286,61 +312,145 @@ enum TableInfo {
}
/**
- * Appends a {@code WHERE} clause together with a condition for searching
the specified object.
- * This method delegates to {@link #where(EPSGDataAccess, Class,
StringBuilder)} with the type
- * of the given object, except that some object properties may be
inspected for resolving ambiguities.
+ * Returns a key which describes the type and/or the number of dimensions
of the given object.
+ * The current implementation relies on the fact that {@link
GeographicCRS} is the only type
+ * for which the current <abbr>EPSG</abbr> database distinguishes the
number of dimensions,
+ * but callers should not depend on this assumption as it may change in
any future version.
*
- * @param factory the factory which is writing a <abbr>SQL</abbr>
statement.
- * @param object the object to search in the database.
- * @param buffer where to append the {@code WHERE} clause.
+ * <h4>Maintenance note</h4>
+ * If the implementation of this method is modified, then the extraction
of {@code dimension} and
+ * {@code userType} properties in the {@link #appendWhere(EPSGDataAccess,
Object, StringBuilder)}
+ * method body must be updated accordingly.
*/
- final void where(final EPSGDataAccess factory, final IdentifiedObject
object, final StringBuilder buffer) {
- Class<?> userType = object.getClass();
+ static Object toCacheKey(final IdentifiedObject object) {
if (object instanceof GeodeticCRS) {
final CoordinateSystem cs = ((GeodeticCRS)
object).getCoordinateSystem();
if (cs instanceof EllipsoidalCS) {
- userType = GeographicCRS.class;
- } else if (cs instanceof CartesianCS || cs instanceof SphericalCS)
{
- userType = DefaultGeocentricCRS.class;
+ return cs.getDimension();
}
}
- where(factory, userType, buffer);
+ return object.getClass();
}
/**
- * Appends a {@code WHERE} clause together with a condition for searching
the most specific subtype,
- * if such condition can be added. The clause appended by this method
looks like the following example
- * (details may vary because of enumeration values):
+ * Extracts the type from a value computed by {@link
#toCacheKey(IdentifiedObject)}.
+ *
+ * @param object value computed by {@link #toCacheKey(IdentifiedObject)}.
+ * @return the class of the object to search, ignoring the number of
dimensions.
+ * @throws ClassCastException if the given object has not been created by
{@link #toCacheKey(IdentifiedObject)}.
+ */
+ static Class<?> typeOfCacheKey(final Object object) {
+ if (object instanceof Integer) {
+ return GeographicCRS.class;
+ }
+ return (Class<?>) object;
+ }
+
+ /**
+ * Appends a {@code WHERE} clause together with a condition for searching
the most specific subtype.
+ * The clause appended by this method looks like the following example:
*
* {@snippet lang="sql" :
- * WHERE COORD_REF_SYS_KIND LIKE 'geographic%' AND
+ * WHERE (COORD_REF_SYS_KIND = 'geographic 2D' OR COORD_REF_SYS_KIND =
'geographic 3D') AND
* }
*
- * The caller shall add at least one condition after this method call.
+ * The <abbr>SQL</abbr> fragment will have a trailing {@code WHERE} or
{@code AND} keyword.
+ * Therefore, the caller shall add at least one condition after this
method call.
+ *
+ * <h4>Object type</h4>
+ * The {@code object} argument shall be one of the following types:
+ *
+ * <ul>
+ * <li>An {@link IdentifiedObject} instance.</li>
+ * <li>The {@link Class} of an {@code IdentifiedObject}. It may be an
implementation class.</li>
+ * <li>An opaque key computed by {@link
#toCacheKey(IdentifiedObject)}.</li>
+ * </ul>
+ *
+ * This method returns a generalization of the {@code object} argument:
either a GeoAPI interface,
+ * or {@code object} if it was a cache key computed by {@link
#toCacheKey(IdentifiedObject)}.
*
- * @param factory the factory which is writing a <abbr>SQL</abbr>
statement.
- * @param userType the type specified by the user.
- * @param buffer where to append the {@code WHERE} clause.
- * @return the subtype, or {@link #type} if no subtype was found.
+ * @param factory the factory which is writing a <abbr>SQL</abbr>
statement.
+ * @param object the instance, class or cache key to search in the
database.
+ * @param buffer where to append the {@code WHERE} clause.
+ * @return the {@code object} argument, potentially generalized.
*/
- final Class<?> where(final EPSGDataAccess factory, final Class<?>
userType, final StringBuilder buffer) {
+ final Object appendWhere(final EPSGDataAccess factory, final Object
object, final StringBuilder buffer) {
+ final int dimension; // 0 if not applicable. This is
applicable only to `GeographicCRS`.
+ final Class<?> userType;
+ if (object instanceof Integer) {
+ dimension = (Integer) object;
+ userType = GeographicCRS.class;
+ } else if (object instanceof Class<?>) {
+ userType = (Class<?>) object;
+ dimension = 0;
+ } else if (object instanceof GeodeticCRS) {
+ final CoordinateSystem cs = ((GeodeticCRS)
object).getCoordinateSystem();
+ if (cs instanceof EllipsoidalCS) {
+ userType = GeographicCRS.class;
+ dimension = cs.getDimension(); // Intentionally
restricted to this specific case.
+ } else {
+ if (cs instanceof CartesianCS || cs instanceof SphericalCS) {
+ userType = DefaultGeocentricCRS.class;
+ } else {
+ userType = object.getClass();
+ }
+ dimension = 0;
+ }
+ } else {
+ userType = object.getClass();
+ dimension = 0;
+ }
+ /*
+ * Above code decomposed the given `object`.
+ * The rest of this method builds the SQL.
+ */
buffer.append(" WHERE ");
if (typeColumn != null) {
for (int i=0; i<subTypes.length; i++) {
- final Class<?> candidate = subTypes[i];
- if (candidate.isAssignableFrom(userType)) {
- if (factory.translator.useEnumerations()) {
- buffer.append("CAST(").append(typeColumn).append(" AS
")
-
.append(EPSGInstaller.ENUM_REPLACEMENT).append(')');
- } else {
- buffer.append(typeColumn);
+ final Class<?> subType = subTypes[i];
+ if (subType.isAssignableFrom(userType)) {
+ /*
+ * Found the type to request in the `COORD_REF_SYS_KIND`
or `DATUM_TYPE` columns.
+ * The mixin interfaces need to be handled in a special
way.
+ */
+ String typeName = typeNames[i];
+ if
(DynamicReferenceFrame.class.isAssignableFrom(userType)) {
+ typeName = DYNAMIC_TYPES.getOrDefault(typeName,
typeName);
+ }
+ /*
+ * We may need to look for more than one type if some
information are missing
+ * (for example, the dimension when EPSG distinguishes the
2D and 3D cases).
+ */
+ String[] synonymous = SYNONYMOUS_TYPES.get(typeName);
+ if (synonymous != null && dimension > 0 && dimension <= 9)
{
+ final String suffix = "2D".replace('2', (char) ('0' +
dimension));
+ if (typeName.endsWith(suffix)) {
+ synonymous = null;
+ } else {
+ for (String alternative : synonymous) {
+ if (alternative.endsWith(suffix)) {
+ typeName = alternative;
+ synonymous = null;
+ break;
+ }
+ }
+ }
+ }
+ /*
+ * Build the SQL `WHERE` clause.
+ */
+ buffer.append('(').append(typeColumn).append(" =
'").append(typeName).append('\'');
+ if (synonymous != null) {
+ for (String alternative : synonymous) {
+ buffer.append(" OR ").append(typeColumn).append("
= '").append(alternative).append('\'');
+ }
}
- buffer.append(" LIKE '").append(typeNames[i]).append("%'
AND ");
- return candidate;
+ buffer.append(") AND ");
+ return subType;
}
}
}
- return type;
+ return (dimension != 0) ? dimension : type;
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index 68c7b489ff..3dcd46f972 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -699,14 +699,27 @@ public final class EPSGFactoryTest extends
TestCaseWithLogs {
@Test
public void testDescriptionText() throws FactoryException {
final EPSGFactory factory = dataEPSG.factory();
- assertEquals("World Geodetic System 1984",
factory.getDescriptionText(GeodeticDatum.class,
"6326").get().toString(Locale.US));
- assertEquals("Mean Sea Level",
factory.getDescriptionText(VerticalDatum.class,
"5100").get().toString(Locale.US));
- assertEquals("NTF (Paris) / Nord France",
factory.getDescriptionText(ProjectedCRS.class,
"27591").get().toString(Locale.US));
- assertEquals("NTF (Paris) / France II",
factory.getDescriptionText(ProjectedCRS.class,
"27582").get().toString(Locale.US));
- assertEquals("Ellipsoidal height",
factory.getDescriptionText(CoordinateSystemAxis.class,
"84").get().toString(Locale.US));
+ assertDescriptionStarts("World Geodetic System 1984", factory,
GeodeticDatum.class, 6326);
+ assertDescriptionStarts("Mean Sea Level", factory,
VerticalDatum.class, 5100);
+ assertDescriptionStarts("NTF (Paris) / Nord France", factory,
ProjectedCRS.class, 27591);
+ assertDescriptionStarts("NTF (Paris) / France II", factory,
ProjectedCRS.class, 27582);
+ assertDescriptionStarts("Ellipsoidal height", factory,
CoordinateSystemAxis.class, 84);
loggings.assertNoUnexpectedLog();
}
+ /**
+ * Asserts that the description text for the given code starts with the
expected value.
+ * We do not require a full match because suffix such as "ensemble" may or
may not be present
+ * depending on the version of the <abbr>EPSG</abbr> database.
+ */
+ private static void assertDescriptionStarts(final String expected, final
EPSGFactory factory,
+ final Class<? extends IdentifiedObject> type, final int code)
throws FactoryException
+ {
+ final var description = factory.getDescriptionText(type,
Integer.toString(code));
+ assertTrue(description.isPresent(), expected);
+ assertTrue(description.get().toString(Locale.US).startsWith(expected),
expected);
+ }
+
/**
* Tests the "UTM zone 10N" conversion (EPSG:16010).
*
diff --git
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
index 20346befa9..f4b76da0ad 100644
---
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
+++
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
@@ -67,3 +67,13 @@ CREATE VIEW "Operation Method Type dimension" AS
ORDER BY IS_CONVERSION;
COMMENT ON VIEW "Operation Method Type dimension" IS 'Number of dimensions of
Operation Method types.';
+
+
+--
+-- Summary of types of datum members in datum ensembles.
+--
+CREATE VIEW "Datum Member Type" AS
+ SELECT DISTINCT DATUM_TYPE
+ FROM "Datum" WHERE DATUM_CODE IN (SELECT DATUM_CODE FROM "Datum Ensemble
Member");
+
+COMMENT ON VIEW "Datum Member Type" IS 'Types of datum members in datum
ensembles.';