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 <[email protected]>
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);
         }
         /*

Reply via email to