This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit ba6681f33243bc4a65a55e76f387b471b5999539 Merge: 39ea192688 3bb55654d2 Author: Martin Desruisseaux <[email protected]> AuthorDate: Tue Aug 5 19:36:51 2025 +0200 Merge branch 'geoapi-3.1'. Contains upgrade of `EPSGFactory` for reading EPSG database version 12. .../sis/buildtools/coding/ReorganizeImports.java | 7 +- .../org.apache.sis.feature/main/module-info.java | 3 + .../main/org/apache/sis/coverage/CategoryList.java | 2 +- .../org/apache/sis/coverage/SampleDimension.java | 20 +- .../coverage/grid/CoordinateOperationFinder.java | 43 +- .../apache/sis/coverage/grid/GridDerivation.java | 53 +- .../org/apache/sis/coverage/grid/GridExtent.java | 9 +- .../apache/sis/coverage/grid/SliceGeometry.java | 2 +- .../main/org/apache/sis/coverage/package-info.java | 2 +- .../org/apache/sis/feature/DefaultFeatureType.java | 2 +- .../main/org/apache/sis/feature/Features.java | 4 +- .../main/org/apache/sis/filter/Optimization.java | 6 +- .../main/org/apache/sis/filter/PropertyValue.java | 2 +- .../main/org/apache/sis/filter/TemporalFilter.java | 2 +- .../main/org/apache/sis/image/PixelIterator.java | 2 +- .../main/org/apache/sis/image/TransferType.java | 6 +- .../main/org/apache/sis/image/Transferer.java | 2 +- .../sis/coverage/grid/GridDerivationTest.java | 49 + .../org/apache/sis/feature/FeatureTestCase.java | 10 +- .../org/apache/sis/metadata/MetadataStandard.java | 2 +- .../org/apache/sis/metadata/MetadataVisitor.java | 2 +- .../org/apache/sis/metadata/TreeTableView.java | 2 +- .../sis/metadata/internal/CitationConstant.java | 2 +- .../sis/metadata/iso/citation/Citations.java | 2 +- .../metadata/iso/extent/DefaultVerticalExtent.java | 18 +- .../apache/sis/metadata/iso/extent/Extents.java | 14 +- .../DefaultRepresentativeFraction.java | 4 +- .../iso/maintenance/DefaultScopeDescription.java | 3 +- .../org/apache/sis/metadata/sql/Dispatcher.java | 2 +- .../org/apache/sis/temporal/LenientDateFormat.java | 4 +- .../apache/sis/util/iso/DefaultNameFactory.java | 6 +- .../main/org/apache/sis/util/iso/Types.java | 2 +- .../apache/sis/temporal/LenientDateFormatTest.java | 4 + .../apache/sis/profile/japan/netcdf/GCOM_C.java | 4 +- .../sis/geometry/AbstractDirectPosition.java | 2 +- .../org/apache/sis/geometry/AbstractEnvelope.java | 2 +- .../main/org/apache/sis/geometry/Envelopes.java | 102 +- .../apache/sis/geometry/WraparoundInEnvelope.java | 171 +- .../main/org/apache/sis/io/wkt/Formatter.java | 2 +- .../apache/sis/io/wkt/GeodeticObjectParser.java | 13 +- .../sis/parameter/DefaultParameterDescriptor.java | 2 + .../sis/parameter/DefaultParameterValue.java | 4 +- .../main/org/apache/sis/parameter/Parameters.java | 4 +- .../main/org/apache/sis/parameter/Verifier.java | 81 +- .../sis/referencing/AbstractIdentifiedObject.java | 7 +- .../main/org/apache/sis/referencing/Builder.java | 2 +- .../main/org/apache/sis/referencing/CRS.java | 22 +- .../apache/sis/referencing/NamedIdentifier.java | 2 +- .../sis/referencing/crs/DefaultGeodeticCRS.java | 24 +- .../sis/referencing/datum/AbstractDatum.java | 30 +- .../referencing/datum/DefaultDatumEnsemble.java | 120 +- .../sis/referencing/datum/DefaultEllipsoid.java | 2 +- .../referencing/datum/DefaultGeodeticDatum.java | 16 +- .../referencing/datum/DefaultVerticalDatum.java | 6 +- .../referencing/factory/AuthorityFactoryProxy.java | 17 + .../factory/ConcurrentAuthorityFactory.java | 35 +- .../factory/GeodeticAuthorityFactory.java | 148 +- .../referencing/factory/GeodeticObjectFactory.java | 4 + .../factory/IdentifiedObjectFinder.java | 2 +- .../referencing/factory/IdentifiedObjectSet.java | 77 +- .../referencing/factory/sql/AuthorityCodes.java | 6 +- .../sis/referencing/factory/sql/AxisName.java | 18 +- .../sis/referencing/factory/sql/BursaWolfInfo.java | 210 -- .../factory/sql/CoordinateOperationSet.java | 21 +- .../referencing/factory/sql/EPSGCodeFinder.java | 20 +- .../referencing/factory/sql/EPSGDataAccess.java | 2631 +++++++++++--------- .../sis/referencing/factory/sql/EPSGFactory.java | 4 +- .../sis/referencing/factory/sql/EPSG_Finish.sql | 2 +- .../sis/referencing/factory/sql/EPSG_Prepare.sql | 18 +- .../referencing/factory/sql/ObjectPertinence.java | 154 ++ .../sis/referencing/factory/sql/SQLTranslator.java | 424 ++-- .../sis/referencing/factory/sql/TableInfo.java | 61 +- .../org/apache/sis/referencing/internal/Epoch.java | 2 - .../internal/ParameterizedTransformBuilder.java | 9 + .../internal/PositionalAccuracyConstant.java | 40 +- .../referencing/internal/VerticalDatumTypes.java | 56 +- .../operation/CoordinateOperationContext.java | 28 +- .../operation/CoordinateOperationFinder.java | 27 +- .../operation/CoordinateOperationRegistry.java | 2 +- .../DefaultCoordinateOperationFactory.java | 31 +- .../referencing/operation/SubOperationInfo.java | 13 +- .../referencing/operation/gridded/LoadedGrid.java | 2 +- .../operation/matrix/AffineTransforms2D.java | 6 +- .../sis/referencing/operation/matrix/Matrix1.java | 2 +- .../sis/referencing/operation/matrix/Matrix2.java | 2 +- .../sis/referencing/operation/matrix/Matrix3.java | 2 +- .../sis/referencing/operation/matrix/Matrix4.java | 2 +- .../referencing/operation/matrix/MatrixSIS.java | 2 +- .../referencing/operation/provider/Wraparound.java | 2 +- .../operation/transform/MolodenskyTransform.java | 2 +- .../operation/transform/WraparoundTransform.java | 4 +- .../referencing/privy/CoordinateOperations.java | 5 - .../org/apache/sis/geometry/EnvelopesTest.java | 12 +- .../datum/DefaultGeodeticDatumTest.java | 2 +- .../referencing/factory/sql/EPSGFactoryTest.java | 36 +- .../sis/referencing/factory/sql/TableInfoTest.java | 10 +- .../internal/VerticalDatumTypesTest.java | 32 +- .../InterpolatedGeocentricTransformTest.java | 2 +- .../storage/landsat/LandsatStoreProviderTest.java | 2 +- .../apache/sis/storage/geotiff/GeoTiffStore.java | 4 +- .../org/apache/sis/storage/geotiff/Reader.java | 2 +- .../org/apache/sis/storage/geotiff/writer/ZIP.java | 28 +- .../org/apache/sis/storage/geotiff/WriterTest.java | 41 +- .../apache/sis/storage/netcdf/base/Convention.java | 30 +- .../sis/storage/netcdf/base/GridMapping.java | 134 +- .../org/apache/sis/storage/netcdf/base/Node.java | 4 +- .../apache/sis/io/stream/HyperRectangleWriter.java | 54 +- .../sis/io/stream/SubsampledRectangleWriter.java | 26 +- .../org/apache/sis/io/stream/UpdatableWrite.java | 2 +- .../org/apache/sis/storage/StorageConnector.java | 8 +- .../aggregate/BandAggregateGridResource.java | 2 +- .../aggregate/ConcatenatedGridResource.java | 2 +- .../sis/storage/aggregate/CoverageAggregator.java | 2 +- .../sis/storage/base/MemoryGridResource.java | 29 +- .../storage/image/WritableSingleImageStore.java | 2 +- .../org/apache/sis/storage/CoverageSubsetTest.java | 2 +- .../aggregate/BandAggregateGridResourceTest.java | 2 +- .../sis/storage/aggregate/OpaqueGridResource.java | 2 +- .../sis/storage/base/MemoryGridResourceTest.java | 2 +- .../apache/sis/storage/csv/StoreProviderTest.java | 2 +- .../sis/storage/esri/AsciiGridStoreTest.java | 2 +- .../sis/storage/image/WorldFileStoreTest.java | 2 +- .../apache/sis/storage/wkt/StoreProviderTest.java | 2 +- .../test/org/apache/sis/storage/wkt/StoreTest.java | 2 +- .../apache/sis/storage/xml/StoreProviderTest.java | 2 +- .../test/org/apache/sis/storage/xml/StoreTest.java | 2 +- .../main/org/apache/sis/math/ArrayVector.java | 4 +- .../main/org/apache/sis/math/DecimalFunctions.java | 2 +- .../main/org/apache/sis/math/Vector.java | 6 +- .../main/org/apache/sis/measure/DerivedScalar.java | 4 +- .../org/apache/sis/measure/MeasurementRange.java | 6 +- .../main/org/apache/sis/measure/NumberRange.java | 14 +- .../main/org/apache/sis/measure/Scalar.java | 10 +- .../main/org/apache/sis/system/Semaphores.java | 2 +- .../main/org/apache/sis/util/ArgumentChecks.java | 2 +- .../main/org/apache/sis/util/ArraysExt.java | 20 +- .../main/org/apache/sis/util/Numbers.java | 6 +- .../main/org/apache/sis/util/collection/Cache.java | 11 +- .../org/apache/sis/util/collection/Containers.java | 8 +- .../sis/util/collection/DefaultTreeTable.java | 2 +- .../main/org/apache/sis/util/privy/Constants.java | 10 +- .../main/org/apache/sis/util/privy/Numerics.java | 2 +- .../org/apache/sis/util/resources/Vocabulary.java | 5 + .../sis/util/resources/Vocabulary.properties | 1 + .../sis/util/resources/Vocabulary_fr.properties | 1 + .../test/org/apache/sis/math/VectorTest.java | 2 +- .../org/apache/sis/measure/SystemUnitTest.java | 2 +- .../coveragejson/CoverageJsonStoreTest.java | 2 +- .../apache/sis/gui/coverage/CoverageCanvas.java | 10 +- .../referencing/factory/sql/epsg/DebugTools.sql | 2 +- .../sis/referencing/factory/sql/epsg/README.md | 1 + 151 files changed, 3505 insertions(+), 2120 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/module-info.java index 0fed268ab6,e557fb23e2..74df0536bb --- a/endorsed/src/org.apache.sis.feature/main/module-info.java +++ b/endorsed/src/org.apache.sis.feature/main/module-info.java @@@ -45,13 -45,12 +45,16 @@@ module org.apache.sis.feature org.apache.sis.storage, org.apache.sis.storage.sql, org.apache.sis.storage.shapefile, // In the "incubator" sub-project. - org.apache.sis.cql, // In the "incubator" sub-project. - org.apache.sis.portrayal.map; // In the "incubator" sub-project. + org.apache.sis.portrayal; + + exports org.apache.sis.filter.privy to + org.apache.sis.storage, + org.apache.sis.storage.sql, + org.apache.sis.storage.shapefile; // In the "incubator" sub-project. + exports org.apache.sis.filter.sqlmm to + org.apache.sis.geometry; // In the "incubator" sub-project. + exports org.apache.sis.feature.privy to org.apache.sis.storage, org.apache.sis.storage.xml, diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java index 97f50a521a,c082297388..a27b44bdde --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java @@@ -75,10 -78,13 +75,10 @@@ import org.apache.sis.util.iso.Names * * @author Martin Desruisseaux (IRD, Geomatys) * @author Alexis Manin (Geomatys) - * @version 1.2 + * @version 1.5 - * - * @see org.opengis.metadata.content.SampleDimension - * * @since 1.0 */ -public class SampleDimension implements IdentifiedType, Serializable { +public class SampleDimension implements Serializable { /** * Serial number for inter-operability with different versions. */ @@@ -213,6 -220,25 +213,24 @@@ return name; } + /** + * Returns a concise definition of this sample dimensions. + * This definition may be shown in user interfaces and should be considered as indicative only. + * The default implementation may change between different versions of Apache <abbr>SIS</abbr>. + * + * @return concise definition of the sample dimension. + * + * @since 1.5 + */ - @Override + public InternationalString getDefinition() { + for (Category category : categories) { + if (category.isQuantitative()) { + return category.getName(); + } + } + return getName().toInternationalString(); + } + /** * Returns all categories in this sample dimension. Note that a {@link Category} object may apply to an arbitrary range * of sample values. Consequently, the first element in this collection may not be directly related to the sample value diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java index 081fee01c8,e7cdce107f..c080642aad --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/FeatureTestCase.java @@@ -287,11 -292,11 +287,11 @@@ public abstract class FeatureTestCase e @Test public void testCustomAttribute() { feature = createFeature(DefaultFeatureTypeTest.city()); -- final var wrong = SingletonAttributeTest.parliament(); - final var city = assertInstanceOf(DefaultAttributeType.class, feature.getType().getProperty("city")); - final var casted = new CustomAttribute<>(Features.cast(city, String.class)); - final var city = assertInstanceOf(AttributeType.class, feature.getType().getProperty("city")); - final var cast = new CustomAttribute<>(Features.cast(city, String.class)); ++ final var wrong = SingletonAttributeTest.parliament(); ++ final var city = assertInstanceOf(DefaultAttributeType.class, feature.getType().getProperty("city")); ++ final var cast = new CustomAttribute<>(Features.cast(city, String.class)); - feature.setProperty(casted); + feature.setProperty(cast); setAttributeValue("city", "Utopia", "Atlantide"); var exception = assertThrows(IllegalArgumentException.class, () -> feature.setProperty(wrong)); diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java index b425ce2b84,e4a5d16c39..012f9b39d3 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/Extents.java @@@ -421,13 -418,13 +421,13 @@@ public final class Extents extends Stat @OptionalCandidate public static MeasurementRange<Double> getVerticalRange(final Extent extent) { MeasurementRange<Double> range = null; - RealizationMethod selectedMethod = null; + VerticalDatumType selectedMethod = null; if (extent != null) try { for (final VerticalExtent element : nonNull(extent.getVerticalElements())) { - double min = element.getMinimumValue(); - double max = element.getMaximumValue(); + Double min = element.getMinimumValue(); + Double max = element.getMaximumValue(); final VerticalCRS crs = element.getVerticalCRS(); - RealizationMethod method = null; + VerticalDatumType method = null; Unit<?> unit = null; if (crs != null) { final VerticalDatum datum = crs.getDatum(); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java index d047ec4b8b,e473ae0c0f..4e1266cee8 --- 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 @@@ -577,7 -567,7 +578,7 @@@ class GeodeticObjectParser extends Math final Element anchor = element.pullElement(OPTIONAL, WKTKeywords.Anchor); final Map<String,Object> properties = parseMetadataAndClose(element, name, null); if (anchor != null) { - properties.put(Datum.ANCHOR_POINT_KEY, anchor.pullString("anchorDefinition")); - properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor.pullString("anchorDefinition")); ++ properties.put(Datum.ANCHOR_POINT_KEY, anchor.pullString(DefaultGeodeticDatum.ANCHOR_DEFINITION_KEY)); anchor.close(ignoredElements); } return properties; @@@ -1458,12 -1444,12 +1459,12 @@@ return null; } final String name = element.pullString("name"); - RealizationMethod method = null; + VerticalDatumType 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 { @@@ -1950,9 -1939,9 +1951,9 @@@ * But sometimes the axis (which was not available when we created the datum) provides * more information. Verify if we can have a better type now, and if so rebuild the datum. */ - if (datum.getRealizationMethod().isEmpty()) { + if (datum.getVerticalDatumType() == VerticalDatumType.OTHER_SURFACE) { - 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) { + if (type != VerticalDatumType.OTHER_SURFACE) { final DatumFactory datumFactory = factories.getDatumFactory(); datum = datumFactory.createVerticalDatum(IdentifiedObjects.getProperties(datum), type); } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java index a4bcb99c7e,6cde7480be..f62d17c438 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java @@@ -183,13 -181,6 +183,15 @@@ public class AbstractIdentifiedObject e */ public static final String DEPRECATED_KEY = "deprecated"; + /** + * Key for the <code>{@value}</code> property to be given to the + * {@code ObjectFactory.createFoo(Map, ...)} methods. + * This is used for setting the value to be returned by {@link #getDomains()}. ++ * ++ * @since 1.5 + */ - static final String DOMAINS_KEY = "domains"; ++ public static final String DOMAINS_KEY = "domains"; + /** * The name for this object or code. Shall never be {@code null}. * @@@ -601,14 -590,7 +603,15 @@@ * @since 0.6 */ public Optional<InternationalString> getDescription() { - return Optional.ofNullable((name != null) ? name.getDescription() : null); ++ @SuppressWarnings("LocalVariableHidesMemberVariable") + final ReferenceIdentifier name = getName(); + if (name instanceof ImmutableIdentifier) { + return Optional.ofNullable(((ImmutableIdentifier) name).getDescription()); + } + if (name instanceof DefaultIdentifier) { + return Optional.ofNullable(((DefaultIdentifier) name).getDescription()); + } + return Optional.empty(); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java index cabcc1e865,3bbfaa0340..864983ab28 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java @@@ -97,24 -95,6 +97,48 @@@ public class AbstractDatum extends Abst */ private static final long serialVersionUID = 5380816794438838309L; ++ /** ++ * Key for the <code>{@value}</code> property to be given to the ++ * {@code DatumFactory.createFoo(Map, ...)} methods. ++ * This is used for setting the value to be returned by {@link #getAnchorDefinition()}. ++ * ++ * @see DatumFactory ++ * @see #getAnchorDefinition() ++ * ++ * @since 1.5 ++ */ ++ public static final String ANCHOR_DEFINITION_KEY = "anchorDefinition"; ++ ++ /** ++ * Key for the <code>{@value}</code> property to be given to the ++ * {@code DatumFactory.createFoo(Map, ...)} methods. ++ * This is used for setting the value to be returned by {@link #getAnchorEpoch()}. ++ * ++ * @see DatumFactory ++ * @see #getAnchorEpoch() ++ * ++ * @since 1.5 ++ */ ++ public static final String ANCHOR_EPOCH_KEY = "anchorEpoch"; ++ + /** + * Key for the <code>{@value}</code> property to be given to the + * {@code DatumFactory.createFoo(Map, ...)} methods. + * This is used for setting the value to be returned by {@link #getPublicationDate()}. + * + * @since 1.5 + */ + public static final String PUBLICATION_DATE_KEY = "publicationDate"; + + /** + * Key for the <code>{@value}</code> property to be given to the + * {@code DatumFactory.createFoo(Map, ...)} methods. + * This is used for setting the value to be returned by {@link #getConventionalRS()}. + * + * @since 1.5 + */ + public static final String CONVENTIONAL_RS_KEY = "conventionalRS"; + /** * Description, possibly including coordinates, of the point or points used to anchor the datum to the Earth. * Also known as the "origin", especially for Engineering and Image Datums. @@@ -172,7 -152,7 +196,7 @@@ * <td>{@link InternationalString} or {@link String}</td> * <td>{@link #getAnchorDefinition()}</td> * </tr><tr> - * <td>{@code "anchorEpoch"}</td> - * <td>{@value org.opengis.referencing.datum.Datum#ANCHOR_EPOCH_KEY}</td> ++ * <td>{@value #ANCHOR_EPOCH_KEY}</td> * <td>{@link Temporal}</td> * <td>{@link #getAnchorEpoch()}</td> * </tr><tr> @@@ -210,9 -190,10 +234,9 @@@ * * @param properties the properties to be given to the identified object. */ - @SuppressWarnings("deprecation") public AbstractDatum(final Map<String,?> properties) { super(properties); - anchorDefinition = Types.toInternationalString(properties, "anchorDefinition"); + anchorDefinition = Types.toInternationalString(properties, ANCHOR_DEFINITION_KEY); if (anchorDefinition == null) { anchorDefinition = Types.toInternationalString(properties, ANCHOR_POINT_KEY); } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java index 2347f87a0c,064b1e7c62..9ff2aa3232 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java @@@ -143,9 -149,9 +144,9 @@@ public class DefaultDatumEnsemble<D ext * @param properties the properties to be given to the identified object. * @param members datum or reference frames which are members of this ensemble. * @param accuracy inaccuracy introduced through use of this ensemble (mandatory). - * @param type the base type of datum members contained in this ensemble. - * @throws ClassCastException if a member is not an instance of {@code type}. + * @param memberType the base type of datum members contained in this ensemble. + * @throws ClassCastException if a member is not an instance of {@code memberType}. - * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two + * @throws IllegalArgumentException if a member is an instance of {@code DatumEnsemble}, of if at least two * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found. * * @see #create(Map, Collection, PositionalAccuracy) @@@ -169,13 -175,15 +170,13 @@@ * * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p> * - * @param ensemble the ensemble to copy. - * @param type the base type of datum members contained in this ensemble. - * @throws ClassCastException if a member is not an instance of {@code type}. + * @param ensemble the ensemble to copy. + * @param memberType the base type of datum members contained in this ensemble. + * @throws ClassCastException if a member is not an instance of {@code memberType}. - * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two + * @throws IllegalArgumentException if a member is an instance of {@code DatumEnsemble}, of if at least two * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found. - * - * @see #castOrCopy(DatumEnsemble) */ - protected DefaultDatumEnsemble(final DefaultDatumEnsemble<? extends D> ensemble, final Class<? super D> type) { - protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble, final Class<D> memberType) { ++ protected DefaultDatumEnsemble(final DefaultDatumEnsemble<? extends D> ensemble, final Class<D> memberType) { super(ensemble); members = List.copyOf(ensemble.getMembers()); ensembleAccuracy = Objects.requireNonNull(ensemble.getEnsembleAccuracy()); @@@ -184,26 -192,25 +185,26 @@@ /** * Verifies this ensemble. All members shall be instances of the specified type and shall have - * the same conventional reference system. No member can be an instance of {@link DatumEnsemble}. + * the same conventional reference system. No member can be an instance of {@code DatumEnsemble}. * - * @param type the base type of datum members contained in this ensemble. - * @throws ClassCastException if a member is not an instance of {@code type}. + * @param memberType the base type of datum members contained in this ensemble. + * @throws ClassCastException if a member is not an instance of {@code memberType}. - * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two + * @throws IllegalArgumentException if a member is an instance of {@code DatumEnsemble}, of if at least two * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found. */ - private void validate(final Class<? super D> type) { + private void validate(final Class<D> memberType) { IdentifiedObject rs = null; for (final D datum : members) { - if (datum instanceof DatumEnsemble<?>) { + if (datum instanceof DefaultDatumEnsemble<?>) { throw new IllegalArgumentException( - Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", DatumEnsemble.class)); + Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "members", DefaultDatumEnsemble.class)); } - if (!type.isInstance(datum)) { + if (!memberType.isInstance(datum)) { throw new ClassCastException( - Errors.format(Errors.Keys.IllegalClass_2, type, datum.getClass())); + Errors.format(Errors.Keys.IllegalClass_2, memberType, Classes.getClass(datum))); } - final IdentifiedObject dr = datum.getConventionalRS().orElse(null); + if (!(datum instanceof AbstractDatum)) continue; + final IdentifiedObject dr = ((AbstractDatum) datum).getConventionalRS().orElse(null); if (dr != null) { if (rs == null) { rs = dr; @@@ -233,9 -240,84 +234,38 @@@ final Collection<? extends D> members, final PositionalAccuracy accuracy) { - return Factory.forMemberType(null, properties, List.copyOf(members), accuracy); + return Factory.forMemberType(Datum.class, null, properties, List.copyOf(members), accuracy); + } + - /** - * Returns a SIS ensemble implementation with the values of the given arbitrary implementation. - * This method performs the first applicable action in the following choices: - * - * <ul> - * <li>If the given object is {@code null}, then this method returns {@code null}.</li> - * <li>Otherwise, if the given object is already an instance of - * {@code DefaultDatumEnsemble}, then it is returned unchanged.</li> - * <li>Otherwise, a new {@code DefaultDatumEnsemble} instance is created using the - * {@linkplain #DefaultDatumEnsemble(DatumEnsemble, Class) copy constructor} and returned. - * Note that this is a <em>shallow</em> copy operation, - * because the other properties contained in the given object are not recursively copied.</li> - * </ul> - * - * The returned ensemble may implement the {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum}, - * {@link ParametricDatum} or {@link EngineeringDatum} interface if all members are instances of the same interface. - * - * @param <D> the type of datum members contained in the ensemble. - * @param object the object to get as a SIS implementation, or {@code null} if none. - * @return a SIS implementation containing the values of the given object (may be the - * given object itself), or {@code null} if the argument was null. - */ - public static <D extends Datum> DefaultDatumEnsemble<D> castOrCopy(final DatumEnsemble<D> object) { - if (object == null || object instanceof DefaultDatumEnsemble<?>) { - return (DefaultDatumEnsemble<D>) object; - } - return Factory.forMemberType(Datum.class, object, null, List.copyOf(object.getMembers()), object.getEnsembleAccuracy()); - } - + /** + * Returns this datum ensemble as a collection of datum of the given type. + * This method casts the datum members, it does not copy or rewrite them. + * However, the returned {@code DatumEnsemble} may be a different instance. + * + * @param <N> compile-time value of {@code memberType}. + * @param memberType the new desired type of datum members. + * @return an ensemble of datum of the given type. + * @throws ClassCastException if at least one member is not an instance of the specified type. + */ + public <N extends Datum> DefaultDatumEnsemble<N> cast(final Class<N> memberType) { + for (final D member : members) { + if (!memberType.isInstance(member)) { + throw new ClassCastException(Errors.format(Errors.Keys.IllegalClass_2, memberType, Classes.getClass(member))); + } + } + /* + * At this point, we verified that all members are of the requested type. + * Now verify if the ensemble as a whole is also of the requested type. + * This part is not mandatory however. + */ + @SuppressWarnings("unchecked") + DefaultDatumEnsemble<N> ensemble = (DefaultDatumEnsemble<N>) this; + if (!memberType.isInstance(ensemble)) { + ensemble = Factory.forMemberType(memberType, ensemble, null, ensemble.members, ensemble.ensembleAccuracy); + } + return ensemble; } - /** - * Returns the GeoAPI interface implemented by this class. - * The SIS implementation returns {@code DatumEnsemble.class}. - * - * <h4>Note for implementers</h4> - * Subclasses usually do not need to override this method since GeoAPI does not define {@code DatumEnsemble} - * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their - * own set of interfaces. - * - * @return the datum interface implemented by this class. - */ - @Override - @SuppressWarnings("unchecked") - public Class<? extends DatumEnsemble<D>> getInterface() { - return (Class) DatumEnsemble.class; - } - /* * NOTE: a previous version provided the following method: * @@@ -252,8 -335,9 +283,8 @@@ * * @return datum or reference frames which are members of this ensemble. */ - @Override @SuppressWarnings("ReturnOfCollectionOrArrayField") // Collection is unmodifiable. - public Collection<D> getMembers() { + public final Collection<D> getMembers() { // Must be final for type safety. See `forMemberType(…)` return members; } @@@ -636,7 -832,8 +669,8 @@@ check: if (it.hasNext()) * Should never happen if the parameterized type of {@code members} is respected. */ static <D extends Datum> DefaultDatumEnsemble<D> forMemberType( + final Class<? extends Datum> memberType, - final DatumEnsemble<? extends D> object, + final DefaultDatumEnsemble<? extends D> object, final Map<String,?> properties, final List<? extends D> members, final PositionalAccuracy accuracy) diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java index eff6a7bb30,8ba7eb177d..5318757721 --- 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 @@@ -202,25 -229,15 +202,25 @@@ public class DefaultVerticalDatum exten } /** - * Returns the method through which this vertical reference frame is realized. + * Returns the type of this datum, or infers the type from the datum name if no type were specified. + * The latter case occurs after unmarshalling, since GML 3.2 does not contain any attribute for the datum type. + * It may also happen if the datum were created using reflection. * - * @return method through which this vertical reference frame is realized. + * <p>This method uses heuristic rules and may be changed in any future SIS version.</p> * - * @since 2.0 (temporary version number until this branch is released) + * <p>No synchronization needed; this is not a problem if this value is computed twice. + * This method returns only existing immutable instances.</p> + * + * @see #getVerticalDatumType() + * @see #getTypeElement() */ - @Override - public Optional<RealizationMethod> getRealizationMethod() { - return Optional.ofNullable(method); + private VerticalDatumType type() { + VerticalDatumType t = type; + if (t == null) { + final ReferenceIdentifier name = super.getName(); - type = t = VerticalDatumTypes.guess(name != null ? name.getCode() : null, super.getAlias(), null); ++ type = t = VerticalDatumTypes.fromDatum(name != null ? name.getCode() : null, super.getAlias(), null); + } + return t; } /** @@@ -394,7 -430,7 +394,7 @@@ protected String formatTo(final Formatter formatter) { super.formatTo(formatter); if (formatter.getConvention().majorVersion() == 1) { - formatter.append(VerticalDatumTypes.toLegacy(type())); - formatter.append(VerticalDatumTypes.toLegacyCode(getVerticalDatumType())); ++ formatter.append(VerticalDatumTypes.toLegacyCode(type())); return WKTKeywords.Vert_Datum; } return formatter.shortOrLong(WKTKeywords.VDatum, WKTKeywords.VerticalDatum); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java index e0ea64a60c,68cfaace7e..20f5dfb954 --- 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 @@@ -32,6 -32,6 +32,9 @@@ import org.opengis.util.InternationalSt import org.apache.sis.util.resources.Errors; import org.apache.sis.util.privy.Strings; ++// Specific to the main branch: ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; ++ /** * Delegates object creations to one of the {@code create} methods in a backing {@code AuthorityFactory}. @@@ -199,6 -197,17 +202,20 @@@ 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 { ++ static final AuthorityFactoryProxy<DefaultDatumEnsemble<?>> ENSEMBLE = ++ new AuthorityFactoryProxy<DefaultDatumEnsemble<?>>((Class) DefaultDatumEnsemble.class, AuthorityFactoryIdentifier.Type.DATUM) { ++ @Override DefaultDatumEnsemble<?> 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); ++ @Override DefaultDatumEnsemble<?> createFromAPI(AuthorityFactory factory, String code) throws FactoryException { ++ if (factory instanceof GeodeticAuthorityFactory) { ++ return ((GeodeticAuthorityFactory) factory).createDatumEnsemble(code); ++ } ++ throw new FactoryException("Unsupported factory."); + } + }; + static final AuthorityFactoryProxy<Datum> DATUM = new AuthorityFactoryProxy<Datum>(Datum.class, AuthorityFactoryIdentifier.Type.DATUM) { @Override Datum create(GeodeticAuthorityFactory factory, String code) throws FactoryException { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java index 1b19c2c77b,955389f63f..2649aa60dd --- 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 @@@ -65,6 -65,6 +65,9 @@@ import org.apache.sis.system.Shutdown // Specific to the main and geoapi-3.1 branches: import org.apache.sis.util.collection.BackingStoreException; ++// Specific to the main branch: ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; ++ /** * A concurrent authority factory that caches all objects created by another factory. @@@ -1101,7 -1143,33 +1104,37 @@@ public abstract class ConcurrentAuthori } /** - * 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> + * ++ * <div class="warning"><b>Upcoming API change — generalization</b><br> ++ * The element type will be changed to the {@code DatumEnsemble} interface ++ * when GeoAPI will provide it (tentatively in GeoAPI 3.1).</div> ++ * + * @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)) { ++ public DefaultDatumEnsemble<?> createDatumEnsemble(final String code) throws FactoryException { ++ if (isDefault(DefaultDatumEnsemble.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 --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java index 0020f59393,c684889ab8..dec2641dbf --- 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 @@@ -42,11 -42,6 +42,12 @@@ import org.apache.sis.referencing.privy import org.apache.sis.util.iso.AbstractFactory; import org.apache.sis.util.resources.Errors; +// Specific to the main branch: +import org.apache.sis.referencing.cs.DefaultParametricCS; +import org.apache.sis.referencing.crs.DefaultParametricCRS; ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; +import org.apache.sis.referencing.datum.DefaultParametricDatum; + /** * Creates geodetic objects from codes defined by an authority. @@@ -345,13 -326,9 +348,13 @@@ public abstract class GeodeticAuthority * <tr><td>EPSG:4984</td> <td>World Geodetic System 1972</td></tr> * </table> * + * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the + * {@link GeodeticCRS} parent interface. This is because ISO 19111 does not defines specific interface + * for the geocentric case. Users should assign the return value to a {@code GeodeticCRS} type.</div> + * * <h4>Default implementation</h4> * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)} and casts the result. - * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown. + * If the result cannot be cast, then a {@link NoSuchAuthorityCodeException} is thrown. * * @param code value allocated by authority. * @return the coordinate reference system for the given code. @@@ -457,11 -452,8 +460,11 @@@ * * <h4>Default implementation</h4> * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)} and casts the result. - * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown. + * If the result cannot be cast, then a {@link NoSuchAuthorityCodeException} is thrown. * + * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed + * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div> + * * @param code value allocated by authority. * @return the coordinate reference system for the given code. * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. @@@ -575,6 -567,46 +578,50 @@@ return cast(ImageCRS.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> + * ++ * <div class="warning"><b>Upcoming API change — generalization</b><br> ++ * The element type will be changed to the {@code DatumEnsemble} interface ++ * when GeoAPI will provide it (tentatively in GeoAPI 3.1).</div> ++ * + * @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); ++ public DefaultDatumEnsemble<?> createDatumEnsemble(final String code) throws NoSuchAuthorityCodeException, FactoryException { ++ return cast(DefaultDatumEnsemble.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}. @@@ -704,11 -746,8 +761,11 @@@ * * <h4>Default implementation</h4> * The default implementation delegates to {@link #createDatum(String)} and casts the result. - * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown. + * If the result cannot be cast, then a {@link NoSuchAuthorityCodeException} is thrown. * + * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed + * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div> + * * @param code value allocated by authority. * @return the datum for the given code. * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. @@@ -997,11 -1036,8 +1054,11 @@@ * * <h4>Default implementation</h4> * The default implementation delegates to {@link #createCoordinateSystem(String)} and casts the result. - * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown. + * If the result cannot be cast, then a {@link NoSuchAuthorityCodeException} is thrown. * + * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed + * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div> + * * @param code value allocated by authority. * @return the coordinate system for the given code. * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java index fe2721d39f,9d7ef4775f..0797fabecc --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticObjectFactory.java @@@ -657,6 -632,6 +657,10 @@@ public class GeodeticObjectFactory exte /** * Creates a datum ensemble from a collection of members and an ensemble accuracy. * ++ * <div class="warning"><b>Upcoming API change — generalization</b><br> ++ * The element type will be changed to the {@code DatumEnsemble} interface ++ * when GeoAPI will provide it (tentatively in GeoAPI 3.1).</div> ++ * * @param <D> the type of datum contained in the ensemble. * @param properties name and other properties to give to the new object. * @param members datum or reference frames which are members of the datum ensemble. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java index f7005d08db,be28e30680..72fca57f7c --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java @@@ -109,9 -110,10 +110,9 @@@ final class CoordinateOperationSet exte } /** - * Creates a coordinate operation for the specified EPSG code. + * Creates a coordinate operation for the specified <abbr>EPSG</abbr> code. */ @Override - @SuppressWarnings("deprecation") protected CoordinateOperation createObject(final String code) throws FactoryException { final Integer base = projections.get(code); if (base != null) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java index 5bdef1b29d,cf8ecff2c0..8034b0bb4f --- 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 @@@ -87,8 -89,9 +89,8 @@@ import org.apache.sis.referencing.inter 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 static org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION; -import org.apache.sis.referencing.internal.ServicesForMetadata; import org.apache.sis.parameter.DefaultParameterDescriptor; import org.apache.sis.parameter.DefaultParameterDescriptorGroup; import org.apache.sis.system.Loggers; @@@ -121,12 -125,9 +124,14 @@@ import org.apache.sis.measure.NumberRan import org.apache.sis.measure.Units; import org.apache.sis.pending.jdk.JDK16; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.Identifier; -import org.opengis.referencing.ObjectDomain; +// Specific to the main branch: ++import org.opengis.referencing.ObjectFactory; +import org.apache.sis.referencing.cs.DefaultParametricCS; ++import org.apache.sis.referencing.datum.AbstractDatum; +import org.apache.sis.referencing.datum.DefaultParametricDatum; +import org.apache.sis.referencing.factory.GeodeticObjectFactory; +import org.apache.sis.referencing.internal.ServicesForMetadata; +import org.apache.sis.temporal.TemporalDate; /** @@@ -1104,9 -1255,42 +1259,42 @@@ search: try (ResultSet result = execute * @return the name together with a set of properties. */ @SuppressWarnings("ReturnOfCollectionOrArrayField") - private Map<String,Object> createProperties(final String table, String name, final Integer code, - CharSequence remarks, final boolean deprecated) throws SQLException, FactoryException + private Map<String,Object> createProperties(final String table, + final Integer code, + String name, // May be replaced by an alias. + final CharSequence description, + final String extentCode, // Legacy from EPSG version 9. + final String scope, // Legacy from EPSG version 9. + final CharSequence remarks, + final boolean deprecated) + throws SQLException, FactoryException { + /* + * Request for extent may cause a recursive call to `createCoordinateReferenceSystem(…)`, + * se we need to fetch and store the extent before to populate the `properties` map. + */ + final Extent extent = (extentCode == null) ? null : createExtent(extentCode); + final String actualTable = translator.toActualTableName(table); + /* + * Get all domains for the object identified by the given code. + * The table used nere is new in version 10 of EPSG database. + * We have to create the extents outside the `while` loop for + * the same reason as above for `extent`. + */ - ObjectDomain[] domains = null; ++ DefaultObjectDomain[] domains = null; + if (translator.isUsageTableFound()) { + final var extents = new ArrayList<String>(); + final var scopes = new ArrayList<Integer>(); + getUsages(actualTable, code, extents, scopes); + if (!extents.isEmpty()) { - domains = new ObjectDomain[extents.size()]; ++ domains = new DefaultObjectDomain[extents.size()]; + for (int i=0; i<domains.length; i++) { + domains[i] = new DefaultObjectDomain( + getScope(scopes.get(i)), + owner.createExtent(extents.get(i))); + } + } + } /* * Search for aliases. Note that searching for the object code is not sufficient. We also need to check if the * record is really from the table we are looking for since different tables may have objects with the same ID. @@@ -1188,34 -1379,15 +1383,15 @@@ properties.put(IdentifiedObject.REMARKS_KEY, remarks); properties.put(AbstractIdentifiedObject.LOCALE_KEY, locale); properties.put(ReferencingFactoryContainer.MT_FACTORY, owner.mtFactory); - return properties; - } - - /** - * Returns the name, aliases and domain of validity for the {@link IdentifiedObject} to construct. - * - * @param table the table on which a query has been executed. - * @param name the name for the {@link IdentifiedObject} to construct. - * @param code the EPSG code of the object to construct. - * @param domainCode the code for the domain of validity, or {@code null} if none. - * @param scope the scope, or {@code null} if none. - * @param remarks remarks, or {@code null} if none. - * @param deprecated {@code true} if the object to create is deprecated. - * @return the name together with a set of properties. - */ - private Map<String,Object> createProperties(final String table, final String name, final Integer code, - final String domainCode, String scope, final String remarks, final boolean deprecated) - throws SQLException, FactoryException - { - if ("?".equals(scope)) { // EPSG sometimes uses this value for unspecified scope. - scope = null; + if (domains != null) { - properties.put(IdentifiedObject.DOMAINS_KEY, domains); ++ properties.put(AbstractIdentifiedObject.DOMAINS_KEY, domains); + } + if (scope != null && !scope.equals(UNKNOWN_SCOPE)) { // Should be always NULL since EPSG version 10. - properties.put(ObjectDomain.SCOPE_KEY, scope); ++ properties.put(Datum.SCOPE_KEY, scope); } - @SuppressWarnings("LocalVariableHidesMemberVariable") - final Map<String,Object> properties = createProperties(table, name, code, remarks, deprecated); - if (domainCode != null) { - properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, owner.createExtent(domainCode)); + if (extent != null) { // Should be always NULL since EPSG version 10. - properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent); ++ properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, extent); } - properties.put(Datum.SCOPE_KEY, scope); return properties; } @@@ -1312,6 -1486,50 +1490,59 @@@ throw noSuchAuthorityCode(IdentifiedObject.class, code); } + /** + * The {@code CRSFactory.createFoo(…)} or {@code DatumFactory.createFoo(…)} method to invoke. + * This is a convenience used by some {@link EPSGDataAccess} {@code createFoo(String)} methods when + * the factory method to invoke has been decided but the properties map has not yet been populated. + * + * @see Proxy + */ + @FunctionalInterface + private interface FactoryCall<F extends Factory, R extends IdentifiedObject> { + /** + * Creates a <abbr>CRS</abbr> or datum. + * + * @param factory the factory to use for creating the object. + * @param properties the properties to give to the object. + * @return the object created from the given properties. + * @throws FactoryException if the factory cannot create the object. + */ + R create(F factory, Map<String, Object> properties) throws FactoryException; + } + + /** + * Invokes the {@code create(…)} method of the given constructor in a block which ensures that recursive method + * calls will be redirected to this factory. This is needed only when the constructor may indirectly invoke the + * {@link org.apache.sis.referencing.CRS#getAuthorityFactory(String)} method. + */ + private <F extends Factory, R extends IdentifiedObject> R create( + final FactoryCall<F, R> constructor, + final F factory, + final Map<String, Object> properties) throws FactoryException + { + final ThreadLocal<CRSAuthorityFactory> caller = ParameterizedTransformBuilder.CREATOR; + final CRSAuthorityFactory old = caller.get(); + caller.set(owner); + try { + return constructor.create(factory, properties); + } finally { + if (old != null) { + caller.set(old); + } else { + caller.remove(); + } + } + } + ++ /** ++ * Returns the geodetic factory to use by casting the given one if possible. ++ */ ++ private static GeodeticObjectFactory extended(final ObjectFactory factory) { ++ return (factory instanceof GeodeticObjectFactory) ++ ? (GeodeticObjectFactory) factory ++ : GeodeticObjectFactory.provider(); ++ } ++ /** * Creates an arbitrary coordinate reference system from a code. * The returned object will typically be an instance of {@link GeographicCRS}, {@link ProjectedCRS}, @@@ -1376,97 -1600,109 +1613,115 @@@ * The following switch statement should have a case for all "epsg_crs_kind" values enumerated * in the "EPSG_Prepare.sql" file, except that the values in this Java code are in lower cases. */ - final CRSFactory crsFactory = owner.crsFactory; - final CoordinateReferenceSystem crs; switch (type.toLowerCase(Locale.US)) { - /* ---------------------------------------------------------------------- - * GEOGRAPHIC CRS + /* ────────────────────────────────────────────────────────────────────── + * GEOCENTRIC CRS * - * NOTE: `createProperties` MUST be invoked after any call to another - * `createFoo` method. Consequently, do not factor out. - * ---------------------------------------------------------------------- */ + * NOTE: all values must be extracted from the `ResultSet` + * before to invoke any `owner.createFoo(…)` method. + * ────────────────────────────────────────────────────────────────────── */ + case "geocentric": { + final String csCode = getString(code, result, 8); + final String datumCode = getString(code, result, 9); + final CoordinateSystem cs = owner.createCoordinateSystem(csCode); // Do not inline the `getString(…)` calls. + final GeodeticDatum datumOrEnsemble = owner.createGeodeticDatum(datumCode); - final DatumEnsemble<GeodeticDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class); ++ final DefaultDatumEnsemble<GeodeticDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class); + final GeodeticDatum datum = (ensemble == null) ? datumOrEnsemble : null; + if (cs instanceof CartesianCS) { + final var c = (CartesianCS) cs; - constructor = (factory, metadata) -> factory.createGeodeticCRS(metadata, datum, ensemble, c); ++ constructor = (factory, metadata) -> ++ (ensemble != null) ? extended(factory).createGeodeticCRS(metadata, datum, ensemble, c) ++ : factory.createGeocentricCRS(metadata, datum, c); + } else if (cs instanceof SphericalCS) { + final var c = (SphericalCS) cs; - constructor = (factory, metadata) -> factory.createGeodeticCRS(metadata, datum, ensemble, c); ++ constructor = (factory, metadata) -> ++ (ensemble != null) ? extended(factory).createGeodeticCRS(metadata, datum, ensemble, c) ++ : factory.createGeocentricCRS(metadata, datum, c); + } else { + throw new FactoryDataException(error().getString( + Errors.Keys.IllegalCoordinateSystem_1, cs.getName())); + } + break; + } + /* ────────────────────────────────────────────────────────────────────── + * GEOGRAPHIC CRS + * ────────────────────────────────────────────────────────────────────── */ case "geographic 2d": case "geographic 3d": { - Integer csCode = getInteger(code, result, 8); + Integer csCode = getInteger(code, result, 8); + String datumCode = getOptionalString(result, 9); + final GeodeticDatum datumOrEnsemble; + if (datumCode == null) { + String baseCode = getString(code, result, 10, 9); + datumOrEnsemble = owner.createGeographicCRS(baseCode).getDatum(); + } else { + datumOrEnsemble = owner.createGeodeticDatum(datumCode); + } if (replaceDeprecatedCS) { - csCode = DEPRECATED_CS.getOrDefault(csCode, csCode); + csCode = replaceDeprecatedCS(csCode); } final EllipsoidalCS cs = owner.createEllipsoidalCS(csCode.toString()); - final String datumCode = getOptionalString(result, 9); - final GeodeticDatum datum; - if (datumCode != null) { - datum = owner.createGeodeticDatum(datumCode); - } else { - final String geoCode = getString(code, result, 10, 9); - result.close(); // Must be closed before call to createGeographicCRS(String) - ensureNoCycle(GeographicCRS.class, epsg); - try { - datum = owner.createGeographicCRS(geoCode).getDatum(); - } finally { - endOfRecursion(GeographicCRS.class, epsg); - } - } - crs = crsFactory.createGeographicCRS(createProperties("Coordinate Reference System", - name, epsg, area, scope, remarks, deprecated), datum, cs); - final DatumEnsemble<GeodeticDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class); ++ final DefaultDatumEnsemble<GeodeticDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, GeodeticDatum.class); + final GeodeticDatum datum = (ensemble == null) ? datumOrEnsemble : null; - constructor = (factory, metadata) -> factory.createGeographicCRS(metadata, datum, ensemble, cs); ++ constructor = (factory, metadata) -> ++ (ensemble != null) ? extended(factory).createGeographicCRS(metadata, datum, ensemble, cs) ++ : factory.createGeographicCRS(metadata, datum, cs); break; } - /* ---------------------------------------------------------------------- + /* ────────────────────────────────────────────────────────────────────── * PROJECTED CRS * - * NOTE: This method invokes itself indirectly, through createGeographicCRS. - * Consequently, we cannot use `result` anymore after this block. - * ---------------------------------------------------------------------- */ + * NOTE: This method may invoke itself for creating the base CRS, + * in which case the `ResultSet` will be closed. Therefore, + * all values must be extracted from the `ResultSet` before + * to invoke any `owner.createFoo(…)` method. + * ────────────────────────────────────────────────────────────────────── */ case "projected": { - final String csCode = getString(code, result, 8); - final String geoCode = getString(code, result, 10); - final String opCode = getString(code, result, 11); - result.close(); // Must be closed before call to createFoo(String) - ensureNoCycle(ProjectedCRS.class, epsg); + final String csCode = getString(code, result, 8); + final String baseCode = getString(code, result, 10); + final String opCode = getString(code, result, 11); + final Conversion fromBase; try { - final CartesianCS cs = owner.createCartesianCS(csCode); - final Conversion op; + fromBase = (Conversion) owner.createCoordinateOperation(opCode); + } catch (ClassCastException e) { + // Should never happen in a well-formed EPSG database. + // If happen anyway, the ClassCastException cause will give more hints than just the message. + throw (NoSuchAuthorityCodeException) noSuchAuthorityCode(Conversion.class, opCode).initCause(e); + } + final CoordinateReferenceSystem baseCRS; + if (deprecated) { + /* + * If the ProjectedCRS is deprecated, one reason among others may be that it uses one of + * the deprecated coordinate systems. Those deprecated CS used non-linear units like DMS. + * Apache SIS cannot instantiate a ProjectedCRS when the baseCRS uses such units, so we + * set a flag asking to replace the deprecated CS by a supported one. Since that baseCRS + * would not be exactly as defined by EPSG, we must not cache it because we do not want + * `owner.createGeographicCRS(geoCode)` to return that modified CRS. Since the same CRS + * may be recreated every time a deprecated ProjectedCRS is created, we temporarily + * shutdown the loggings in order to avoid the same warning to be logged many time. + */ + final boolean old = quiet; try { - op = (Conversion) owner.createCoordinateOperation(opCode); - } catch (ClassCastException e) { - // Should never happen in a well-formed EPSG database. - // If happen anyway, the ClassCastException cause will give more hints than just the message. - throw (NoSuchAuthorityCodeException) noSuchAuthorityCode(Conversion.class, opCode).initCause(e); - } - final CoordinateReferenceSystem baseCRS; - final boolean suspendParamChecks; - if (!deprecated) { - baseCRS = owner.createCoordinateReferenceSystem(geoCode); - suspendParamChecks = true; - } else { - /* - * If the ProjectedCRS is deprecated, one reason among others may be that it uses one of - * the deprecated coordinate systems. Those deprecated CS used non-linear units like DMS. - * Apache SIS cannot instantiate a ProjectedCRS when the baseCRS uses such units, so we - * set a flag asking to replace the deprecated CS by a supported one. Since that baseCRS - * would not be exactly as defined by EPSG, we must not cache it because we do not want - * `owner.createGeographicCRS(geoCode)` to return that modified CRS. Since the same CRS - * may be recreated every time a deprecated ProjectedCRS is created, we temporarily - * shutdown the loggings in order to avoid the same warning to be logged many time. - */ - final boolean old = quiet; - try { - quiet = true; - replaceDeprecatedCS = true; - baseCRS = createCoordinateReferenceSystem(geoCode); // Do not cache that CRS. - } finally { - replaceDeprecatedCS = false; - quiet = old; - } - /* - * The crsFactory method calls will indirectly create a parameterized MathTransform. - * Their constructor will try to verify the parameter validity. But some deprecated - * CRS had invalid parameter values (they were deprecated precisely for that reason). - * If and only if we are creating a deprecated CRS, temporarily suspend the parameter - * checks. - */ - suspendParamChecks = Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK); - // Try block must be immediately after above line (do not insert any code between). + quiet = true; + replaceDeprecatedCS = true; + baseCRS = createCoordinateReferenceSystem(baseCode); // Do not cache that CRS. + } finally { + replaceDeprecatedCS = false; + quiet = old; } + } else { + baseCRS = owner.createCoordinateReferenceSystem(baseCode); // Use the cache. + } + final CartesianCS cs = owner.createCartesianCS(csCode); + constructor = (factory, metadata) -> { + /* + * The crsFactory method calls will indirectly create a parameterized MathTransform. + * Their constructor will try to verify the parameter validity. But some deprecated + * CRS had invalid parameter values (they were deprecated precisely for that reason). + * If and only if we are creating a deprecated CRS, temporarily suspend the parameter + * checks. + */ + final boolean old = !deprecated || Semaphores.queryAndSet(Semaphores.SUSPEND_PARAMETER_CHECK); try { /* * For a ProjectedCRS, the baseCRS is always geodetic. So in theory we would not @@@ -1479,47 -1715,74 +1734,80 @@@ * We need to check if EPSG database 10+ has more specific information. * See https://issues.apache.org/jira/browse/SIS-518 */ - @SuppressWarnings("LocalVariableHidesMemberVariable") - final Map<String,Object> properties = createProperties("Coordinate Reference System", - name, epsg, area, scope, remarks, deprecated); if (baseCRS instanceof GeographicCRS) { - crs = crsFactory.createProjectedCRS(properties, (GeographicCRS) baseCRS, op, cs); + return factory.createProjectedCRS(metadata, (GeographicCRS) baseCRS, fromBase, cs); } else { - crs = crsFactory.createDerivedCRS(properties, baseCRS, op, cs); + return factory.createDerivedCRS(metadata, baseCRS, fromBase, cs); } } finally { - Semaphores.clear(Semaphores.SUSPEND_PARAMETER_CHECK, suspendParamChecks); + Semaphores.clearIfFalse(Semaphores.SUSPEND_PARAMETER_CHECK, old); } - } finally { - endOfRecursion(ProjectedCRS.class, epsg); - } + }; break; } - /* ---------------------------------------------------------------------- + /* ────────────────────────────────────────────────────────────────────── * VERTICAL CRS - * ---------------------------------------------------------------------- */ + * ────────────────────────────────────────────────────────────────────── */ case "vertical": { - final VerticalCS cs = owner.createVerticalCS (getString(code, result, 8)); - final VerticalDatum datum = owner.createVerticalDatum(getString(code, result, 9)); - crs = crsFactory.createVerticalCRS(createProperties("Coordinate Reference System", - name, epsg, area, scope, remarks, deprecated), datum, cs); + final String csCode = getString(code, result, 8); + final String datumCode = getString(code, result, 9); + final VerticalCS cs = owner.createVerticalCS(csCode); // Do not inline the `getString(…)` calls. + final VerticalDatum datumOrEnsemble = owner.createVerticalDatum(datumCode); - final DatumEnsemble<VerticalDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, VerticalDatum.class); ++ final DefaultDatumEnsemble<VerticalDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, VerticalDatum.class); + final VerticalDatum datum = (ensemble == null) ? datumOrEnsemble : null; - constructor = (factory, metadata) -> factory.createVerticalCRS(metadata, datum, ensemble, cs); ++ constructor = (factory, metadata) -> ++ (ensemble != null) ? extended(factory).createVerticalCRS(metadata, datum, ensemble, cs) ++ : factory.createVerticalCRS(metadata, datum, cs); break; } - /* ---------------------------------------------------------------------- + /* ────────────────────────────────────────────────────────────────────── * TEMPORAL CRS * - * NOTE : The original EPSG database does not define any temporal CRS. - * This block is a SIS-specific extension. - * ---------------------------------------------------------------------- */ + * NOTE : As of version 12, the EPSG database does not define temporal CRS. + * This block is a SIS─specific extension. + * ────────────────────────────────────────────────────────────────────── */ case "time": case "temporal": { - final TimeCS cs = owner.createTimeCS (getString(code, result, 8)); - final TemporalDatum datum = owner.createTemporalDatum(getString(code, result, 9)); - crs = crsFactory.createTemporalCRS(createProperties("Coordinate Reference System", - name, epsg, area, scope, remarks, deprecated), datum, cs); + final String csCode = getString(code, result, 8); + final String datumCode = getString(code, result, 9); + final TimeCS cs = owner.createTimeCS(csCode); // Do not inline the `getString(…)` calls. + final TemporalDatum datumOrEnsemble = owner.createTemporalDatum(datumCode); - final DatumEnsemble<TemporalDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, TemporalDatum.class); ++ final DefaultDatumEnsemble<TemporalDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, TemporalDatum.class); + final TemporalDatum datum = (ensemble == null) ? datumOrEnsemble : null; - constructor = (factory, metadata) -> factory.createTemporalCRS(metadata, datum, ensemble, cs); ++ constructor = (factory, metadata) -> ++ (ensemble != null) ? extended(factory).createTemporalCRS(metadata, datum, ensemble, cs) ++ : factory.createTemporalCRS(metadata, datum, cs); + break; + } + /* ────────────────────────────────────────────────────────────────────── + * ENGINEERING CRS + * ────────────────────────────────────────────────────────────────────── */ + case "engineering": { + final String csCode = getString(code, result, 8); + final String datumCode = getString(code, result, 9); + final CoordinateSystem cs = owner.createCoordinateSystem(csCode); // Do not inline the `getString(…)` calls. + final EngineeringDatum datumOrEnsemble = owner.createEngineeringDatum(datumCode); - final DatumEnsemble<EngineeringDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, EngineeringDatum.class); ++ final DefaultDatumEnsemble<EngineeringDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, EngineeringDatum.class); + final EngineeringDatum datum = (ensemble == null) ? datumOrEnsemble : null; - constructor = (factory, metadata) -> factory.createEngineeringCRS(metadata, datum, ensemble, cs); ++ constructor = (factory, metadata) -> ++ (ensemble != null) ? extended(factory).createEngineeringCRS(metadata, datum, ensemble, cs) ++ : factory.createEngineeringCRS(metadata, datum, cs); break; } - /* ---------------------------------------------------------------------- + /* ────────────────────────────────────────────────────────────────────── + * PARAMETRIC CRS + * ────────────────────────────────────────────────────────────────────── */ + case "parametric": { + final String csCode = getString(code, result, 8); + final String datumCode = getString(code, result, 9); - final ParametricCS cs = owner.createParametricCS(csCode); // Do not inline the `getString(…)` calls. - final ParametricDatum datumOrEnsemble = owner.createParametricDatum(datumCode); - final DatumEnsemble<ParametricDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, ParametricDatum.class); - final ParametricDatum datum = (ensemble == null) ? datumOrEnsemble : null; - constructor = (factory, metadata) -> factory.createParametricCRS(metadata, datum, ensemble, cs); ++ final DefaultParametricCS cs = owner.createParametricCS(csCode); // Do not inline the `getString(…)` calls. ++ final DefaultParametricDatum datumOrEnsemble = owner.createParametricDatum(datumCode); ++ final DefaultDatumEnsemble<DefaultParametricDatum> ensemble = wasDatumEnsemble(datumOrEnsemble, DefaultParametricDatum.class); ++ final DefaultParametricDatum datum = (ensemble == null) ? datumOrEnsemble : null; ++ constructor = (factory, metadata) -> extended(factory).createParametricCRS(metadata, datum, ensemble, cs); + break; + } + /* ────────────────────────────────────────────────────────────────────── * COMPOUND CRS * * NOTE: This method invokes itself recursively. @@@ -1602,6 -1827,29 +1852,29 @@@ return returnValue; } + /** + * Returns the given datum as a datum ensemble if it should be considered as such. + * This method exists because the datum and datum ensemble are stored in the same table, + * and Apache <abbr>SIS</abbr> creates those two kinds of objects with the same method. + * The real type is resolved by inspection of the {@link #createDatum(String)} return value. + * + * <h4>Design restriction</h4> + * We cannot resolve the type with a private field which would be set by {@link #createDatumEnsemble(String)} + * because that method will not be invoked if the datum is fetched from the cache. + * + * @param <D> compile-time value of {@code memberType}. + * @param datum the datum to check if it is a datum ensemble. + * @param memberType the expected type of datum members. + * @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> wasDatumEnsemble(final D datum, final Class<D> memberType) { - if (datum instanceof DatumEnsemble<?>) { - return DefaultDatumEnsemble.castOrCopy((DatumEnsemble<?>) datum).cast(memberType); ++ private static <D extends Datum> DefaultDatumEnsemble<D> wasDatumEnsemble(final D datum, final Class<D> memberType) { ++ if (datum instanceof DefaultDatumEnsemble<?>) { ++ return ((DefaultDatumEnsemble<?>) datum).cast(memberType); + } + return null; + } + /** * Creates an arbitrary datum from a code. The returned object will typically be an * instance of {@link GeodeticDatum}, {@link VerticalDatum} or {@link TemporalDatum}. @@@ -1689,27 -1922,23 +1947,23 @@@ * 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. */ - final DatumFactory datumFactory = owner.datumFactory; - final Datum datum; switch (type.toLowerCase(Locale.US)) { - /* - * The "geodetic" case invokes createProperties(…) indirectly through calls to - * createEllipsoid(String) and createPrimeMeridian(String), so we must protect - * the properties map from changes. - */ + 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 BursaWolfParameters[] param = createBursaWolfParameters(meridian, epsg); - if (param != null) { - properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, param); - } - datum = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian); + final String ellipsoidCode = getString(code, result, 12); + final String meridianCode = getString(code, result, 13); + final Ellipsoid ellipsoid = owner.createEllipsoid(ellipsoidCode); // Do not inline the `getString(…)` calls. + final PrimeMeridian meridian = owner.createPrimeMeridian(meridianCode); + constructor = (factory, metadata) -> - (dynamic != null) ? factory.createGeodeticDatum(metadata, ellipsoid, meridian, dynamic) ++ (dynamic != null) ? extended(factory).createGeodeticDatum(metadata, ellipsoid, meridian, dynamic) + : factory.createGeodeticDatum(metadata, ellipsoid, meridian); break; } case "vertical": { - datum = datumFactory.createVerticalDatum(properties, VerticalDatumType.GEOIDAL); - final RealizationMethod method = getRealizationMethod(getOptionalInteger(result, 14)); ++ final VerticalDatumType method = getRealizationMethod(getOptionalInteger(result, 14)); + constructor = (factory, metadata) -> - (dynamic != null) ? factory.createVerticalDatum(metadata, method, dynamic) ++ (dynamic != null) ? extended(factory).createVerticalDatum(metadata, method, dynamic) + : factory.createVerticalDatum(metadata, method); break; } /* @@@ -1726,28 -1955,33 +1980,33 @@@ } catch (RuntimeException e) { throw new FactoryDataException(resources().getString(Resources.Keys.DatumOriginShallBeDate), e); } - datum = datumFactory.createTemporalDatum(properties, TemporalDate.toDate(originDate)); - constructor = (factory, metadata) -> factory.createTemporalDatum(metadata, originDate); ++ constructor = (factory, metadata) -> factory.createTemporalDatum(metadata, TemporalDate.toDate(originDate)); break; } /* - * Straightforward case. + * Straightforward cases. */ - case "engineering": { - datum = datumFactory.createEngineeringDatum(properties); - break; - } - case "parametric": { - datum = ServicesForMetadata.createParametricDatum(properties, datumFactory); - break; - } - default: { - throw new FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type)); - } + case "engineering": constructor = DatumFactory::createEngineeringDatum; break; - case "parametric": constructor = DatumFactory::createParametricDatum; break; ++ case "parametric": constructor = (factory, metadata) -> extended(factory).createParametricDatum(metadata); break; + case "ensemble": constructor = createDatumEnsemble(epsg); break; + default: throw new FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type)); } + /* + * Map of properties should be populated only after we extracted all + * information needed from the `ResultSet`, because it may be closed. + */ + final IdentifiedObject conventionalRS = createConventionalRS(convRSCode); + @SuppressWarnings("LocalVariableHidesMemberVariable") + final Map<String,Object> properties = createProperties( + "Datum", epsg, name, null, area, scope, remarks, deprecated); - 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, conventionalRS); ++ properties.put(AbstractDatum.ANCHOR_DEFINITION_KEY, anchor); ++ properties.put(AbstractDatum.ANCHOR_EPOCH_KEY, epoch); ++ properties.put(AbstractDatum.PUBLICATION_DATE_KEY, publish); ++ properties.put(AbstractDatum.CONVENTIONAL_RS_KEY, conventionalRS); + properties.values().removeIf(Objects::isNull); + final Datum datum = constructor.create(owner.datumFactory, properties); returnValue = ensureSingleton(datum, returnValue, code); - if (result.isClosed()) { - break; // Because of the recursive call done by createBursaWolfParameters(…). - } + if (result.isClosed()) break; // See createProperties(…) for explanation. } } catch (SQLException exception) { throw databaseFailure(Datum.class, code, exception); @@@ -1807,71 -2023,115 +2048,115 @@@ } } } - int size = bwInfos.size(); - if (size == 0) { - return null; + final var accuracy = PositionalAccuracyConstant.ensemble(max); + final List<Datum> members = createComponents( + GeodeticAuthorityFactory::createDatum, + "DatumEnsembleMember", + "SELECT DATUM_CODE" + + " FROM \"DatumEnsembleMember\"" + + " WHERE DATUM_ENSEMBLE_CODE = ?" + + " ORDER BY DATUM_SEQUENCE", code); - return (factory, metadata) -> DefaultDatumEnsemble.castOrCopy(factory.createDatumEnsemble(metadata, members, accuracy)); ++ return (factory, metadata) -> extended(factory).createDatumEnsemble(metadata, members, accuracy); + } + + /** + * Creates the members of a geodetic object. This method gets all member codes and closes the + * result set before to create the members, because the creation of a member causes recursive + * invocation to some {@code create(…)} methods of this factory. + * + * @param <C> the type of component objects. + * @param constructor the method to invoke for creating the components. + * @param table a key uniquely identifying the caller. + * @param sql the SQL statement to use for creating the {@link PreparedStatement} object. + * @param parent the code of the container object. + * @return all components for the given parent. + */ + private <C extends IdentifiedObject> List<C> createComponents(final Proxy<C> constructor, + final String table, + final String sql, + final Integer parent) + throws SQLException, FactoryException + { + final var codes = new ArrayList<String>(); + try (ResultSet result = executeQueryForCodes(table, sql, parent)) { + while (result.next()) { + codes.add(getString(parent, result, 1)); + } } - /* - * Sort the infos in preference order. The "ORDER BY" clause above was not enough; - * we also need to take the "Supersession" table in account. Once the sorting is done, - * keep only one Bursa-Wolf parameters for each datum. - */ - if (size > 1) { - final BursaWolfInfo[] codes = bwInfos.toArray(new BursaWolfInfo[size]); - sort("Coordinate_Operation", codes); - bwInfos.clear(); - BursaWolfInfo.filter(owner, codes, bwInfos); - size = bwInfos.size(); + final var members = new ArrayList<C>(codes.size()); + for (String code : codes) { + members.add(constructor.create(owner, code)); } - /* - * Now, iterate over the results and fetch the parameter values for each BursaWolfParameters object. + return members; + } + + /** + * Delegates object creations to one of the {@code create(…)} methods. This is used for creating + * the components of a geodetic object. Invoking the {@code create(…)} method of this interface + * will often result in invocation of a public {@code create(…)} method of the enclosing class. + * + * @param <C> the type of geodetic objects to create. + * + * @see FactoryCall + * @see org.apache.sis.referencing.factory.AuthorityFactoryProxy + */ + @FunctionalInterface + private interface Proxy<C extends IdentifiedObject> { + /** + * Creates a component from the given code by delegating + * (indirectly) to a method of the enclosing class. + * + * @param factory the factory to use for creating the component. + * @param code authority code of the component to create. + * @return the component created from the given code. + * @throws FactoryException if an error occurred while creating the component. */ - final var parameters = new BursaWolfParameters[size]; - final Locale locale = getLocale(); - int count = 0; - for (int i=0; i<size; i++) { - final BursaWolfInfo info = bwInfos.get(i); - final GeodeticDatum datum; - ensureNoCycle(BursaWolfParameters.class, code); // See comment at the begining of this method. - try { - datum = owner.createGeodeticDatum(String.valueOf(info.target)); - } finally { - endOfRecursion(BursaWolfParameters.class, code); - } - /* - * Accept only Bursa-Wolf parameters between datum that use the same prime meridian. - * This is for avoiding ambiguity about whether longitude rotation should be applied - * before or after the datum change. This check is useless for EPSG dataset 8.9 since - * all datum seen by this method use Greenwich. But we nevertheless perform this check - * as a safety for future evolution or customized EPSG dataset. - */ - if (!equalsIgnoreMetadata(meridian, datum.getPrimeMeridian())) { - continue; - } - final var bwp = new BursaWolfParameters(datum, info.getDomainOfValidity(owner)); - try (ResultSet result = executeQuery("BursaWolfParameters", - "SELECT PARAMETER_CODE," + - " PARAMETER_VALUE," + - " UOM_CODE" + - " FROM \"Coordinate_Operation Parameter Value\"" + - " WHERE COORD_OP_CODE = ?" + - " AND COORD_OP_METHOD_CODE = ?", info.operation, info.method)) + C create(GeodeticAuthorityFactory factory, String code) throws FactoryException; + } + + /** + * 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); + if (code == null) { + return null; + } + final Long cacheKey = cacheKey(4, code); + var returnValue = (IdentifiedObject) localCache.get(cacheKey); + if (returnValue == null) { + try (ResultSet result = executeQueryForCodes( + "ConventionalRS", + "SELECT"+ /* column 1 */ " CONVENTIONAL_RS_CODE," + + /* column 2 */ " CONVENTIONAL_RS_NAME," + + /* column 3 */ " REMARKS," + + /* column 4 */ " DEPRECATED" + + " FROM \"ConventionalRS\"" + + " WHERE CONVENTIONAL_RS_CODE = ?", code)) { while (result.next()) { - BursaWolfInfo.setBursaWolfParameter(bwp, - getInteger(info.operation, result, 1), - getDouble (info.operation, result, 2), - owner.createUnit(getString(info.operation, result, 3)), locale); + 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); + /* + * Map of properties should be populated only after we extracted all + * information needed from the `ResultSet`, because it may be closed. + */ + @SuppressWarnings("LocalVariableHidesMemberVariable") + final Map<String,Object> properties = createProperties( + "ConventionalRS", epsg, name, null, null, null, remarks, deprecated); + returnValue = ensureSingleton(new AbstractIdentifiedObject(properties), returnValue, code); + if (result.isClosed()) break; // See createProperties(…) for explanation. } } - if (info.isFrameRotation()) { - // Coordinate frame rotation (9607): same as 9606, - // except for the sign of rotation parameters. - bwp.reverseRotation(); - } - parameters[count++] = bwp; + localCache.put(cacheKey, returnValue); } - return ArraysExt.resize(parameters, count); + return returnValue; } /** @@@ -2178,10 -2529,7 +2554,7 @@@ } case WKTKeywords.spherical: { switch (dimension) { - case 2: if (csFactory instanceof GeodeticObjectFactory) { - cs = ((GeodeticObjectFactory) csFactory).createSphericalCS(properties, axes[0], axes[1]); - } - break; - case 2: cs = csFactory.createSphericalCS(properties, axes[0], axes[1]); break; ++ case 2: cs = extended(csFactory).createSphericalCS(properties, axes[0], axes[1]); break; case 3: cs = csFactory.createSphericalCS(properties, axes[0], axes[1], axes[2]); break; } break; @@@ -2202,7 -2550,7 +2575,7 @@@ } case WKTKeywords.parametric: { switch (dimension) { - case 1: cs = ServicesForMetadata.createParametricCS(properties, axes[0], csFactory); break; - case 1: cs = csFactory.createParametricCS(properties, axes[0]); break; ++ case 1: cs = extended(csFactory).createParametricCS(properties, axes[0]); break; } break; } @@@ -2381,7 -2704,40 +2729,42 @@@ if (returnValue == null) { throw noSuchAuthorityCode(AxisName.class, String.valueOf(code)); } - axisNames.put(code, returnValue); + localCache.put(cacheKey, returnValue); + } + return returnValue; + } + + /** + * Returns the realization method for the specified code. ++ * Returned as the legacy {@link VerticalDatumType} because ++ * {@code RealizationMethod} did not existed in GeoAPI 3.0. + * + * @param code code of the realization method, or {@code null} if none. - * @return realization method, or {@code null} if the given code was null. ++ * @return realization method, or {@code GEOIDAL} if the given code was null. + */ - private RealizationMethod getRealizationMethod(final Integer code) throws FactoryException, SQLException { ++ private VerticalDatumType getRealizationMethod(final Integer code) throws FactoryException, SQLException { + assert Thread.holdsLock(this); + if (code == null) { - return null; ++ return VerticalDatumType.GEOIDAL; + } + final Long cacheKey = cacheKey(2, code); - var returnValue = (RealizationMethod) localCache.get(cacheKey); ++ var returnValue = (VerticalDatumType) localCache.get(cacheKey); + if (returnValue == null && code != null) { + try (ResultSet result = executeQueryForCodes( + "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)); ++ throw noSuchAuthorityCode(VerticalDatumType.class, String.valueOf(code)); + } + localCache.put(cacheKey, returnValue); } return returnValue; } @@@ -2598,14 -2970,20 +2997,20 @@@ next: while (r.next() case 0: valueDomain = null; break; default: valueDomain = new EPSGParameterDomain(units); break; case 1: valueDomain = MeasurementRange.create(Double.NEGATIVE_INFINITY, false, - Double.POSITIVE_INFINITY, false, CollectionsExt.first(units)); break; + Double.POSITIVE_INFINITY, false, + CollectionsExt.first(units)); break; } + /* + * Map of properties should be populated only after we extracted all + * information needed from the `ResultSet`, because it may be closed. + */ @SuppressWarnings("LocalVariableHidesMemberVariable") - final Map<String,Object> properties = - createProperties("Coordinate_Operation Parameter", name, epsg, isReversible, deprecated); + final Map<String,Object> properties = createProperties( + "Coordinate_Operation Parameter", epsg, name, null, null, null, isReversible, deprecated); - properties.put(Identifier.DESCRIPTION_KEY, description); + properties.put(ImmutableIdentifier.DESCRIPTION_KEY, description); final var descriptor = new DefaultParameterDescriptor<>(properties, 1, 1, type, valueDomain, null, null); returnValue = ensureSingleton(descriptor, returnValue, code); + if (result.isClosed()) break; // See createProperties(…) for explanation. } } catch (SQLException exception) { throw databaseFailure(OperationMethod.class, code, exception); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java index 366b8fa587,4ffdbd4f7a..26c70067cf --- 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 @@@ -113,10 -110,10 +114,10 @@@ final class TableInfo "DATUM_NAME", "DATUM_TYPE", new Class<?>[] { GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class, - TemporalDatum.class, ParametricDatum.class}, + TemporalDatum.class, DefaultParametricDatum.class}, new String[] {"geodetic", "vertical", "engineering", "temporal", "parametric"}, // Same comment as in the CRS case above. - null), + null, true), ELLIPSOID = new TableInfo(Ellipsoid.class, "\"Ellipsoid\"", diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java index a78390b5c2,8eef7854ae..2b754a9e0a --- 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 @@@ -81,6 -77,17 +81,11 @@@ public final class VerticalDatumTypes i */ 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. - */ - private VerticalDatumTypes() { - } - /** * Returns a pseudo-realization method for ellipsoidal heights. * <strong>The use of this method is deprecated</strong> as ellipsoidal height @@@ -115,14 -122,14 +120,14 @@@ * @param code the legacy vertical datum code. * @return the vertical datum type, or {@code null} if none. */ - public static VerticalDatumType fromLegacy(final int code) { - public static RealizationMethod fromLegacyCode(final int code) { ++ public static VerticalDatumType fromLegacyCode(final int code) { switch (code) { // case 2000: return null; // CS_VD_Other - case 2001: return RealizationMethod.valueOf(ORTHOMETRIC); // CS_VD_Orthometric + case 2001: return VerticalDatumType.valueOf(ORTHOMETRIC); // CS_VD_Orthometric case 2002: return ellipsoidal(); // CS_VD_Ellipsoidal - case 2003: return RealizationMethod.valueOf(BAROMETRIC); // CS_VD_AltitudeBarometric - case 2005: return RealizationMethod.GEOID; // CS_VD_GeoidModelDerived - case 2006: return RealizationMethod.TIDAL; // CS_VD_Depth + case 2003: return VerticalDatumType.BAROMETRIC; // CS_VD_AltitudeBarometric + case 2005: return VerticalDatumType.GEOIDAL; // CS_VD_GeoidModelDerived + case 2006: return VerticalDatumType.DEPTH; // CS_VD_Depth default: return null; } } @@@ -131,13 -138,13 +136,12 @@@ * Returns the legacy code for the datum type, or 2000 (other surface) if unknown. * This method is used for WKT 1 formatting. * - * @param type the vertical datum type, or {@code null} if unknown. + * @param method the vertical datum type, or {@code null} if unknown. * @return the legacy code for the given datum type, or 0 if unknown. */ -- @SuppressWarnings("deprecation") - public static int toLegacy(final VerticalDatumType type) { - if (type != null) { - switch (type.name().toUpperCase(Locale.US)) { + public static int toLegacyCode(final VerticalDatumType method) { + if (method != null) { + switch (method.name().toUpperCase(Locale.US)) { case ORTHOMETRIC: return 2001; // CS_VD_Orthometric case ELLIPSOIDAL: return 2002; // CS_VD_Ellipsoidal case BAROMETRIC: return 2003; // CS_VD_AltitudeBarometric @@@ -148,6 -155,80 +152,28 @@@ return 2000; } - /** - * Returns the vertical datum type from a realization method. - * If the given method cannot be mapped to a legacy type, then this method returns "other surface". - * This is because the vertical datum type was a mandatory property in legacy OGC/ISO standards. - * This method is used for writing GML documents older than GML 3.2. - * - * <p>Note: this is renamed {@code toLegacyName(RealizationMethod)} on the GeoAPI 4.0 branch.</p> - * - * @param method the realization method, or {@code null}. - * @return the vertical datum type (never null). - */ - @SuppressWarnings("deprecation") - public static VerticalDatumType fromMethod(final RealizationMethod method) { - if (method == RealizationMethod.GEOID) return VerticalDatumType.GEOIDAL; - if (method == RealizationMethod.TIDAL) return VerticalDatumType.DEPTH; - if (method != null) { - return VerticalDatumType.valueOf(method.name().toUpperCase(Locale.US)); - } - return VerticalDatumType.OTHER_SURFACE; - } - - /** - * Returns the realization method from a vertical datum type. - * This method is used for reading GML documents older than GML 3.2. - * - * <p>Note: this is renamed {@code fromLegacyName(String)} on the GeoAPI 4.0 branch.</p> - * - * @param type the vertical datum type, or {@code null}. - * @return the realization method, or {@code null} if none. - */ - @SuppressWarnings("deprecation") - public static RealizationMethod toMethod(final VerticalDatumType type) { - return (type != null) ? fromLegacyName(type.name()) : null; - } - - /** - * Returns the realization method from a vertical datum type. - * This method is used for reading GML documents older than GML 3.2. - * - * @param type the vertical datum type, or {@code null}. - * @return the realization method, or {@code null} if none. - */ - 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. + * + * <p>Note: this is {@code fromMethod(String)} on the GeoAPI 4.0 branch.</p> + * + * @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); ++ public static VerticalDatumType fromMethod(final String name) { ++ VerticalDatumType method = fromDatum(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)); ++ method = fromDatum(name.substring(0, s)); + } + if (method == null) { - method = RealizationMethod.valueOf(name); ++ method = VerticalDatumType.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 @@@ -158,12 -239,12 +184,12 @@@ * @param name the name of the datum for which to guess a type, or {@code null} if unknown. * @param aliases the aliases of the datum for which to guess a type, or {@code null} if unknown. * @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. + * @return a datum type, or {@link VerticalDatumType#OTHER_SURFACE} if none can be guessed. */ - public static VerticalDatumType guess(final String name, final Collection<? extends GenericName> aliases, - public static RealizationMethod fromDatum(final String name, final Collection<? extends GenericName> aliases, ++ public static VerticalDatumType fromDatum(final String name, final Collection<? extends GenericName> aliases, final CoordinateSystemAxis axis) { - VerticalDatumType method = guess(name); - RealizationMethod method = fromDatum(name); ++ VerticalDatumType method = fromDatum(name); if (method != null) { return method; } @@@ -205,17 -286,13 +231,23 @@@ * @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 VerticalDatumType guess(final String name) { - private static RealizationMethod fromDatum(final String name) { ++ private static VerticalDatumType fromDatum(final String name) { if (name != null) { if (CharSequences.equalsFiltered("Mean Sea Level", name, Characters.Filter.LETTERS_AND_DIGITS, true)) { - return RealizationMethod.TIDAL; + return VerticalDatumType.GEOIDAL; } - if (name.contains("geoid")) { - return RealizationMethod.GEOID; ++ if (name.regionMatches(true, 0, "geoid", 0, 5)) { ++ return VerticalDatumType.GEOIDAL; ++ } ++ if (name.equalsIgnoreCase("Tidal")) { ++ return VerticalDatumType.DEPTH; ++ } + for (int i=0; i<name.length();) { + final int c = name.codePointAt(i); + if (Character.isLetter(c)) { + return CodeLists.find(VerticalDatumType.class, new VerticalDatumTypes(name)); + } + i += Character.charCount(c); } } return null; diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/internal/VerticalDatumTypesTest.java index 00de10be23,0a167b5be6..b0692cbd2d --- 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 @@@ -52,23 -56,36 +52,35 @@@ public final class VerticalDatumTypesTe } /** - * 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(VerticalDatumType .GEOIDAL, VerticalDatumTypes.fromLegacy(2005)); - assertEquals(VerticalDatumType .DEPTH, VerticalDatumTypes.fromLegacy(2006)); + public void testFromLegacyCode() { + assertEquals(VerticalDatumTypes.ellipsoidal(), VerticalDatumTypes.fromLegacyCode(2002)); - assertEquals(RealizationMethod .GEOID, VerticalDatumTypes.fromLegacyCode(2005)); - assertEquals(RealizationMethod .TIDAL, VerticalDatumTypes.fromLegacyCode(2006)); ++ assertEquals(VerticalDatumType .GEOIDAL, VerticalDatumTypes.fromLegacyCode(2005)); ++ assertEquals(VerticalDatumType .DEPTH, VerticalDatumTypes.fromLegacyCode(2006)); } /** - * Tests the {@link VerticalDatumTypes#toLegacy(VerticalDatumType)} method. + * Tests the {@link VerticalDatumTypes#toLegacyCode(VerticalDatumType)} method. */ @Test - public void testToLegacy() { - assertEquals(2002, VerticalDatumTypes.toLegacy(VerticalDatumType.valueOf("ELLIPSOIDAL"))); - assertEquals(2005, VerticalDatumTypes.toLegacy(VerticalDatumType.GEOIDAL)); - assertEquals(2006, VerticalDatumTypes.toLegacy(VerticalDatumType.DEPTH)); + @SuppressWarnings("deprecation") + public void testToLegacyCode() { + assertEquals(2002, VerticalDatumTypes.toLegacyCode(VerticalDatumType.valueOf("ELLIPSOIDAL"))); + assertEquals(2005, VerticalDatumTypes.toLegacyCode(VerticalDatumType.GEOIDAL)); + assertEquals(2006, VerticalDatumTypes.toLegacyCode(VerticalDatumType.DEPTH)); + } + + /** + * 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()); ++ assertEquals(VerticalDatumType.GEOIDAL, VerticalDatumTypes.fromMethod("Geoid-based")); ++ assertEquals(VerticalDatumType.DEPTH, VerticalDatumTypes.fromMethod("Tidal")); ++ assertTrue(VerticalDatumTypes.LOCAL.equalsIgnoreCase(VerticalDatumTypes.fromMethod("Local").name())); } /** diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java index 5618343620,9c8bc924ba..c6c62b4661 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/GridMapping.java @@@ -396,7 -436,10 +432,9 @@@ final class GridMapping */ if (isSpecified) { final Map<String,?> properties = properties(definition, Convention.GEOGRAPHIC_CRS_NAME, main, datum); - return decoder.getCRSFactory().createGeographicCRS(properties, datum, + return decoder.getCRSFactory().createGeographicCRS( + properties, + datum, - ensemble, defaultDefinitions.geographic().getCoordinateSystem()); } else { return defaultDefinitions.geographic();
