This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-3.1 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 4e0bc740bc0d77e5e2806420a0c5e2f8789cf59e Merge: 236ded5a92 c9641c4260 Author: Martin Desruisseaux <[email protected]> AuthorDate: Sun Aug 31 17:23:57 2025 +0200 Merge branch 'geoapi-4.0' into geoapi-3.1. The merge improves the way that the authority factory search for EPSG codes in the database. This review was necessary because of new datum types, but also improves performance. This merge also fixes more test cases with version 12 of the EPSG database. .../org/apache/sis/console/CRSCommandTest.java | 2 +- .../sis/coverage/grid/DimensionalityReduction.java | 2 +- .../org/apache/sis/coverage/grid/GridExtent.java | 2 +- .../org/apache/sis/coverage/grid/GridGeometry.java | 2 +- .../sis/coverage/grid/ResampledGridCoverage.java | 7 +- .../geometry/wrapper/SpatialOperationContext.java | 9 +- .../org/apache/sis/geometry/wrapper/jts/JTS.java | 5 +- .../coverage/grid/DimensionalityReductionTest.java | 2 +- .../metadata/iso/extent/DefaultVerticalExtent.java | 3 +- .../sis/metadata/privy/NameToIdentifier.java | 2 +- .../sis/metadata/sql/privy/SQLUtilities.java | 61 +- .../org/apache/sis/metadata/sql/privy/Syntax.java | 2 +- .../main/org/apache/sis/temporal/TemporalDate.java | 2 +- .../sis/metadata/sql/privy/SQLUtilitiesTest.java | 4 +- .../sis/openoffice/ReferencingFunctions.java | 2 +- .../sis/openoffice/ReferencingFunctionsTest.java | 5 +- .../org/apache/sis/map/coverage/RenderingData.java | 3 +- .../main/org/apache/sis/portrayal/Canvas.java | 13 +- .../gazetteer/AbstractLocationType.java | 4 +- .../gazetteer/GeohashReferenceSystem.java | 5 +- .../gazetteer/MilitaryGridReferenceSystem.java | 5 +- .../gazetteer/ReferencingByIdentifiers.java | 4 +- .../sis/coordinate/DefaultCoordinateMetadata.java | 4 +- .../sis/geometry/AbstractDirectPosition.java | 6 +- .../org/apache/sis/geometry/AbstractEnvelope.java | 2 +- .../org/apache/sis/geometry/EnvelopeReducer.java | 5 +- .../main/org/apache/sis/geometry/Envelopes.java | 6 +- .../apache/sis/geometry/WraparoundAdjustment.java | 3 +- .../main/org/apache/sis/io/wkt/Formatter.java | 2 +- .../apache/sis/io/wkt/GeodeticObjectParser.java | 125 ++-- .../sis/parameter/DefaultParameterDescriptor.java | 6 +- .../sis/parameter/DefaultParameterValueGroup.java | 8 +- .../parameter/UnmodifiableParameterValueGroup.java | 8 +- .../sis/referencing/AbstractIdentifiedObject.java | 15 +- .../apache/sis/referencing/AuthorityFactories.java | 54 +- .../main/org/apache/sis/referencing/Builder.java | 4 +- .../main/org/apache/sis/referencing/CRS.java | 39 +- .../main/org/apache/sis/referencing/CommonCRS.java | 40 +- .../sis/referencing/DefaultObjectDomain.java | 3 +- .../sis/referencing/EPSGFactoryFallback.java | 41 +- .../apache/sis/referencing/IdentifiedObjects.java | 39 +- .../sis/referencing/StandardDefinitions.java | 70 ++- .../apache/sis/referencing/crs/AbstractCRS.java | 97 +-- .../sis/referencing/crs/AbstractDerivedCRS.java | 4 +- .../sis/referencing/crs/AbstractSingleCRS.java | 53 +- .../sis/referencing/crs/DefaultCompoundCRS.java | 50 +- .../sis/referencing/crs/DefaultDerivedCRS.java | 46 +- .../sis/referencing/crs/DefaultEngineeringCRS.java | 10 + .../sis/referencing/crs/DefaultGeodeticCRS.java | 16 +- .../sis/referencing/crs/DefaultParametricCRS.java | 10 + .../sis/referencing/crs/DefaultProjectedCRS.java | 18 +- .../sis/referencing/crs/DefaultTemporalCRS.java | 9 + .../sis/referencing/crs/DefaultVerticalCRS.java | 10 + .../org/apache/sis/referencing/cs/AbstractCS.java | 93 +-- .../sis/referencing/cs/DefaultCompoundCS.java | 40 +- .../cs/DefaultCoordinateSystemAxis.java | 4 +- .../sis/referencing/datum/AbstractDatum.java | 10 +- .../sis/referencing/datum/DatumOrEnsemble.java | 272 ++++++-- .../referencing/datum/DefaultDatumEnsemble.java | 4 +- .../sis/referencing/datum/DefaultEllipsoid.java | 11 +- .../referencing/datum/DefaultGeodeticDatum.java | 6 +- .../sis/referencing/datum/DefaultImageDatum.java | 4 +- .../referencing/datum/DefaultPrimeMeridian.java | 14 +- .../referencing/datum/DefaultTemporalDatum.java | 4 +- .../referencing/datum/DefaultVerticalDatum.java | 34 +- .../factory/AuthorityFactoryIdentifier.java | 19 +- .../factory/ConcurrentAuthorityFactory.java | 130 +++- .../factory/IdentifiedObjectFinder.java | 686 +++++++++++++-------- .../factory/MultiAuthoritiesFactory.java | 113 +++- .../referencing/factory/sql/AuthorityCodes.java | 177 ++++-- .../factory/sql/CloseableReference.java | 6 + .../referencing/factory/sql/EPSGCodeFinder.java | 395 ++++++++++-- .../referencing/factory/sql/EPSGDataAccess.java | 531 ++++++++-------- .../sis/referencing/factory/sql/EPSGFactory.java | 32 +- .../sis/referencing/factory/sql/EPSGInstaller.java | 27 +- .../factory/sql/InstallationScriptProvider.java | 198 +----- .../referencing/factory/sql/ObjectPertinence.java | 11 +- .../sis/referencing/factory/sql/SQLTranslator.java | 34 +- .../sis/referencing/factory/sql/TableInfo.java | 501 +++++++++------ .../referencing/internal/EPSGParameterDomain.java | 4 +- .../sis/referencing/internal/MergedProperties.java | 4 +- .../referencing/internal/PositionTransformer.java | 6 +- .../apache/sis/referencing/internal/RTreeNode.java | 4 +- .../referencing/internal/VerticalDatumTypes.java | 16 +- .../operation/AbstractCoordinateOperation.java | 20 +- .../operation/AbstractSingleOperation.java | 4 +- .../apache/sis/referencing/operation/CRSPair.java | 7 +- .../operation/CoordinateOperationFinder.java | 2 +- .../operation/CoordinateOperationRegistry.java | 9 +- .../operation/DefaultConcatenatedOperation.java | 1 + .../operation/DefaultOperationMethod.java | 10 +- .../sis/referencing/operation/matrix/Matrices.java | 15 +- .../operation/projection/NormalizedProjection.java | 6 +- .../operation/provider/Mercator1SP.java | 7 +- .../operation/provider/ObliqueMercator.java | 12 +- .../operation/provider/PolarStereographicA.java | 2 +- .../operation/provider/PseudoMercator.java | 7 +- .../operation/provider/TransverseMercator.java | 2 +- .../operation/transform/AbstractMathTransform.java | 2 +- .../operation/transform/ConcatenatedTransform.java | 15 +- .../transform/EllipsoidToRadiusTransform.java | 6 +- .../operation/transform/PoleRotation.java | 1 + .../sis/referencing/privy/AxisDirections.java | 4 +- .../sis/referencing/privy/FilteredIterator.java | 113 ++++ .../org/apache/sis/referencing/privy/LazySet.java | 25 +- .../apache/sis/referencing/privy/WKTUtilities.java | 32 - .../org/apache/sis/referencing/Assertions.java | 69 ++- .../sis/referencing/AuthorityFactoriesTest.java | 2 +- .../test/org/apache/sis/referencing/CRSTest.java | 4 +- .../org/apache/sis/referencing/CommonCRSTest.java | 4 +- .../sis/referencing/EPSGFactoryFallbackTest.java | 2 +- .../sis/referencing/StandardDefinitionsTest.java | 29 + .../factory/CommonAuthorityFactoryTest.java | 4 +- .../factory/ConcurrentAuthorityFactoryTest.java | 2 +- .../factory/GeodeticObjectFactoryTest.java | 3 +- .../factory/IdentifiedObjectFinderTest.java | 3 +- .../referencing/factory/sql/EPSGFactoryTest.java | 59 +- .../referencing/factory/sql/EPSGInstallerTest.java | 20 +- .../factory/sql/EPSGScriptProvider.java | 113 ++++ .../sis/referencing/factory/sql/TableInfoTest.java | 14 +- .../transform/DefaultMathTransformFactoryTest.java | 2 +- .../report/CoordinateOperationMethods.java | 2 +- .../sis/test/integration/ConsistencyTest.java | 63 +- .../integration/CoordinateReferenceSystemTest.java | 33 - .../sis/storage/geotiff/reader/CRSBuilder.java | 6 +- .../apache/sis/storage/netcdf/base/AxisType.java | 2 +- .../apache/sis/storage/netcdf/base/CRSMerger.java | 4 +- .../sis/storage/sql/feature/InfoStatements.java | 14 +- .../sis/storage/sql/feature/SelectionClause.java | 4 +- .../sis/storage/aggregate/CoverageAggregator.java | 2 +- .../apache/sis/storage/aggregate/GroupByCRS.java | 4 +- .../apache/sis/storage/image/WritableStore.java | 2 +- .../apache/sis/setup/InstallationResources.java | 2 +- .../main/org/apache/sis/util/CharSequences.java | 66 +- .../main/org/apache/sis/util/ComparisonMode.java | 146 +++-- .../org/apache/sis/util/LenientComparable.java | 18 +- .../main/org/apache/sis/util/Utilities.java | 2 +- .../org/apache/sis/util/collection/TreeTable.java | 2 +- .../main/org/apache/sis/util/logging/Logging.java | 2 +- .../main/org/apache/sis/util/privy/Constants.java | 1 + .../main/org/apache/sis/util/privy/Numerics.java | 2 +- .../apache/sis/util/privy/SetOfUnknownSize.java | 6 +- .../main/org/apache/sis/util/resources/Errors.java | 7 +- .../apache/sis/util/resources/Errors.properties | 1 - .../apache/sis/util/resources/Errors_fr.properties | 1 - .../test/org/apache/sis/test/Assertions.java | 4 +- .../org/apache/sis/util/CharSequencesTest.java | 5 +- .../main/org/apache/sis/geometries/Geometries.java | 3 +- .../org/apache/sis/geometries/PreparedTIN.java | 12 +- .../apache/sis/geometries/math/AbstractTuple.java | 4 +- .../main/org/apache/sis/geometries/math/Tuple.java | 4 +- .../geometries/mesh/MeshPrimitiveComparator.java | 6 +- .../sis/geometries/mesh/MultiMeshPrimitive.java | 4 +- .../sis/geometries/processor/ProcessorUtils.java | 8 +- .../simplify/greedyinsert/TINBuilder.java | 4 +- netbeans-project/nbproject/project.xml | 1 + optional/build.gradle.kts | 18 +- .../main/org/apache/sis/gui/map/StatusBar.java | 2 +- .../gui/referencing/PositionableProjection.java | 3 +- .../sis/resources/embedded/EmbeddedResources.java | 14 +- .../sis/resources/embedded/package-info.java | 12 +- .../resources/embedded/EmbeddedResourcesTest.java | 40 +- .../apache/sis/resources/embedded/Generator.java | 117 ++-- .../sis/referencing/factory/sql/epsg/.gitignore | 5 +- .../sis/referencing/factory/sql/epsg/Finish.sql | 0 .../sis/referencing/factory/sql/epsg/Prepare.sql | 4 +- .../sis/referencing/factory/sql/epsg/README.md | 13 +- .../factory/sql/epsg/ScriptProvider.java | 32 +- .../referencing/factory/sql/epsg/DebugTools.sql | 10 + .../factory/sql/epsg/ScriptProviderTest.java | 4 +- 170 files changed, 3531 insertions(+), 2161 deletions(-) diff --cc endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java index d4e8343eaa,5644549f9e..fd0f65165a --- a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java +++ b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/portrayal/Canvas.java @@@ -797,8 -796,8 +796,8 @@@ public class Canvas extends Observable public void setDisplayBounds(final Envelope newValue) throws RenderException { ArgumentChecks.ensureNonNull(DISPLAY_BOUNDS_PROPERTY, newValue); final CoordinateReferenceSystem crs = newValue.getCoordinateReferenceSystem(); - if (crs != null && !Utilities.equalsIgnoreMetadata(getDisplayCRS(), crs)) { + if (crs != null && !CRS.equivalent(getDisplayCRS(), crs)) { - throw new MismatchedCoordinateMetadataException(errors().getString( + throw new MismatchedReferenceSystemException(errors().getString( Errors.Keys.IllegalCoordinateSystem_1, IdentifiedObjects.getDisplayName(crs, getLocale()))); } final GeneralEnvelope oldValue = new GeneralEnvelope(displayBounds); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java index 4f4fdb5c20,3c5731c819..9624762637 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java @@@ -127,8 -127,8 +127,8 @@@ public abstract class AbstractDirectPos final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); if (crs != null) { final CoordinateReferenceSystem other = position.getCoordinateReferenceSystem(); - if (other != null && !Utilities.equalsIgnoreMetadata(crs, other)) { + if (other != null && !CRS.equivalent(crs, other)) { - throw new MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS)); + throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS)); } } for (int i=0; i<dimension; i++) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java index e473ae0c0f,da10560a59..3ebf31d761 --- 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 @@@ -1679,10 -1676,10 +1680,10 @@@ class GeodeticObjectParser extends Math final Unit<?> unit = parseUnit(element); final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, false, unit, datum); + cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, false, unit, false, null); final Map<String,?> properties = parseMetadataAndClose(element, name, datum); if (cs instanceof AffineCS) { - return new DefaultImageCRS(properties, datum, (AffineCS) cs); + return factories.getCRSFactory().createImageCRS(properties, datum, (AffineCS) cs); } } catch (FactoryException exception) { throw element.parseFailed(exception); @@@ -2156,15 -2126,37 +2129,37 @@@ } final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, csUnit, geoCRS.getDatum()); - final Map<String,?> properties = parseMetadataAndClose(element, name, conversion); + cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, csUnit, true, null); if (cs instanceof CartesianCS) { + final SingleCRS geoCRS = parseGeodeticCRS(MANDATORY, element, cs.getDimension(), WKTKeywords.ellipsoidal); + if (!(geoCRS instanceof GeodeticCRS)) { + throw new UnparsableObjectException(errorLocale, Errors.Keys.IllegalCRSType_1, + new Object[] {geoCRS.getClass()}, element.offset); + } /* - * TODO: if the CartesianCS is three-dimensional, we need to ensure that the base CRS is also - * three-dimensional. We could do that by parsing the CS before to invoke `parseGeodeticCRS`. + * Parse the projection parameters. If a default linear unit is specified, it will apply to + * all parameters that do not specify explicitly a LengthUnit. If no such crs-wide unit was + * specified, then the default will be degrees. + * + * More specifically §9.3.4 in the specification said about the default units: + * + * - lengths shall be given in the unit for the projected CRS axes. + * - angles shall be given in the unit for the base geographic CRS of the projected CRS. */ + final Unit<Length> linearUnit; + final Unit<Angle> angularUnit; + if (isWKT1 && usesCommonUnits) { + linearUnit = Units.METRE; + angularUnit = Units.DEGREE; + } else { + linearUnit = csUnit; + angularUnit = AxisDirections.getAngularUnit(geoCRS.getCoordinateSystem(), Units.DEGREE); + } + final Conversion conversion = parseDerivingConversion(MANDATORY, element, + isWKT1 ? null : WKTKeywords.Conversion, linearUnit, angularUnit); + final Map<String,?> properties = parseMetadataAndClose(element, name, conversion); final CRSFactory crsFactory = factories.getCRSFactory(); - return crsFactory.createProjectedCRS(properties, (GeodeticCRS) geoCRS, conversion, (CartesianCS) cs); + return crsFactory.createProjectedCRS(properties, (GeographicCRS) geoCRS, conversion, (CartesianCS) cs); } } catch (FactoryException exception) { throw element.parseFailed(exception); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java index fdcf1d4b52,9fc223597c..5d6f57441a --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java @@@ -133,15 -130,14 +130,15 @@@ final class EPSGFactoryFallback extend final boolean ellipsoid = type.isAssignableFrom(Ellipsoid .class); final boolean datum = type.isAssignableFrom(GeodeticDatum.class); final boolean geographic = type.isAssignableFrom(GeographicCRS.class); - final boolean geodetic = type.isAssignableFrom(GeodeticCRS .class); + @SuppressWarnings("deprecation") + final boolean geocentric = type.isAssignableFrom(GeocentricCRS.class); final boolean projected = type.isAssignableFrom(ProjectedCRS .class); - final Set<String> codes = new LinkedHashSet<>(); + final var codes = new LinkedHashSet<String>(); if (pm) codes.add(StandardDefinitions.GREENWICH); for (final CommonCRS crs : CommonCRS.values()) { - if (ellipsoid) add(codes, crs.ellipsoid); - if (datum) add(codes, crs.datum); - if (geodetic) add(codes, crs.geocentric); + if (ellipsoid) add(codes, crs.ellipsoid); + if (datum) add(codes, crs.datum); + if (geocentric) add(codes, crs.geocentric); if (geographic) { add(codes, crs.geographic); add(codes, crs.geo3D); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java index 8ba7eb177d,214fe8f0c7..82a0ae2c47 --- 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 @@@ -241,24 -219,20 +244,44 @@@ public class DefaultVerticalDatum exten } /** - * Returns the realization method if it was explicitly specified, or otherwise tries to guess it. + * Returns the type of this vertical datum. + * + * <h4>Historical note:</h4> + * This property was defined in the ISO 19111 specification published in 2003, + * but removed from the revision published 2007. + * This property provides an information similar to the {@linkplain #getAnchorPoint() anchor definition}, + * but in a programmatic way more suitable to coordinate transformation engines. + * + * @return the type of this vertical datum. + * + * @deprecated As of ISO 19111:2019, the {@code VerticalDatumType} argument is replaced by {@code RealizationMethod}. + */ + @Override + @Deprecated(since = "2.0") // Temporary version number until this branch is released. + public VerticalDatumType getVerticalDatumType() { + return type; + } + ++ /** ++ * Returns the datum type if it was explicitly specified, or otherwise tries to guess it. + * This method may return {@code null} if it cannot guess the method. This is used for compatibility + * with legacy formats such as WKT 1 or GML 3.1, before realization method became a formal property. + */ - private RealizationMethod getOrGuessMethod(final FormattableObject parent) { - return getRealizationMethod().orElseGet(() -> { ++ private VerticalDatumType getOrGuessMethod(final FormattableObject parent) { ++ @SuppressWarnings("LocalVariableHidesMemberVariable") ++ final VerticalDatumType type = getVerticalDatumType(); ++ if (type != null && type != VerticalDatumType.OTHER_SURFACE) { ++ return type; ++ } ++ return VerticalDatumTypes.fromMethod(getRealizationMethod().orElseGet(() -> { + CoordinateSystemAxis axis = null; + if (parent instanceof CoordinateReferenceSystem) { + axis = ((CoordinateReferenceSystem) parent).getCoordinateSystem().getAxis(0); + } + return VerticalDatumTypes.fromDatum(getName().getCode(), getAlias(), axis); - }); ++ })); + } + /** * A vertical reference frame in which some of the defining parameters have time dependency. * The parameter values are valid at the time given by the @@@ -466,10 -420,13 +487,13 @@@ * * @see <a href="http://issues.apache.org/jira/browse/SIS-160">SIS-160: Need XSLT between GML 3.1 and 3.2</a> */ + @SuppressWarnings("deprecation") @XmlElement(name = "verticalDatumType") - @XmlJavaTypeAdapter(CollapsedStringAdapter.class) - private String getTypeElement() { + private VerticalDatumType getTypeElement() { - return Context.isGMLVersion(Context.current(), LegacyNamespaces.VERSION_3_2) ? null : getVerticalDatumType(); + if (Context.isGMLVersion(Context.current(), LegacyNamespaces.VERSION_3_2)) { + return null; + } - return VerticalDatumTypes.toLegacyName(getOrGuessMethod(null)); ++ return getOrGuessMethod(null); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java index 2af9d2d64d,6284d56dae..34d842d425 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java @@@ -16,26 -16,35 +16,37 @@@ */ package org.apache.sis.referencing.factory; + import java.util.Iterator; import java.util.Set; - import java.util.LinkedHashSet; + import java.util.HashSet; import java.util.Objects; + import java.util.function.Function; import java.util.logging.Level; import java.util.logging.LogRecord; - import org.opengis.util.GenericName; import org.opengis.util.FactoryException; --import org.opengis.metadata.Identifier; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.AuthorityFactory; import org.opengis.referencing.NoSuchAuthorityCodeException; - import org.apache.sis.referencing.AbstractIdentifiedObject; + import org.opengis.referencing.datum.Datum; import org.apache.sis.referencing.IdentifiedObjects; + import org.apache.sis.referencing.datum.DatumOrEnsemble; + import org.apache.sis.referencing.privy.FilteredIterator; + import org.apache.sis.referencing.privy.LazySet; import org.apache.sis.util.ComparisonMode; + import org.apache.sis.util.Disposable; import org.apache.sis.util.Utilities; + import org.apache.sis.util.OptionalCandidate; import org.apache.sis.util.privy.Constants; - import org.apache.sis.system.Semaphores; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.logging.Logging; + import org.apache.sis.system.Semaphores; + + // Specific to the geoapi-3.1 and geoapi-4.0 branches: + import org.opengis.referencing.datum.DatumEnsemble; + ++// Specific to the main and geoapi-3.1 branches: ++import org.opengis.referencing.ReferenceIdentifier; + /** * Searches in an authority factory for objects approximately equal to a given object. @@@ -576,9 -570,153 +572,153 @@@ public class IdentifiedObjectFinder * @param object the object looked up. * @return a set of code candidates. * @throws FactoryException if an error occurred while fetching the set of code candidates. + * + * @see IdentifiedObject#getIdentifiers() + * @see AuthorityFactory#getAuthorityCodes(Class) + */ + protected Iterable<String> getCodeCandidates(final IdentifiedObject object) throws FactoryException { + /* + * Undocumented contract: if the Iterable implements `Dispose`, its method will be invoked + * after the first match has been found. This is interpreted as a hint that the iteration + * can stop earlier than it would normally do. + */ + final Class<? extends IdentifiedObject> type = proxy.type.asSubclass(IdentifiedObject.class); + if (domain == Domain.EXHAUSTIVE_VALID_DATASET) { + return factory.getAuthorityCodes(type); + } + final boolean easy = (domain == Domain.DECLARATION); - final Set<Identifier> identifiers = object.getIdentifiers(); ++ final Set<ReferenceIdentifier> identifiers = object.getIdentifiers(); + if (identifiers.isEmpty()) { + return easy ? Set.of() : factory.getAuthorityCodes(type); + } + return new Codes(factory, identifiers, easy ? null : type); + } + + /** + * Union of identifier codes followed by code candidates fetched from the geodetic dataset. + * The codes returned by this iterable are, in this order: + * + * <ol> - * <li>{@link IdentifiedObject#getIdentifiers()} (filtered with {@link #apply(Identifier)})</li> ++ * <li>{@link IdentifiedObject#getIdentifiers()} (filtered with {@link #apply(ReferenceIdentifier)})</li> + * <li>{@link AuthorityFactory#getAuthorityCodes(Class)} (skipped if the class is null)</li> + * </ol> + * + * <h2>Implementation note</h2> + * This class should not keep a reference to the enclosing class (it is static for that reason), + * because some subclasses of {@link IdentifiedObjectFinder} are short-lived data access objects + * holding resources such as database connections. */ - protected Set<String> getCodeCandidates(final IdentifiedObject object) throws FactoryException { - return factory.getAuthorityCodes(proxy.type.asSubclass(IdentifiedObject.class)); - private static final class Codes implements Iterable<String>, Function<Identifier, String>, Disposable { ++ private static final class Codes implements Iterable<String>, Function<ReferenceIdentifier, String>, Disposable { + /** Copy of {@link IdentifiedObjectFinder#factory}. */ + private final AuthorityFactory factory; + + /** The identifiers of the object to search. */ - private final Set<Identifier> identifiers; ++ private final Set<ReferenceIdentifier> identifiers; + + /** Type of objects to request for code candidates, or {@code null} for not requesting code candidates. */ + private Class<? extends IdentifiedObject> type; + + /** Code candidates, created when first needed. This collection may be costly to create and/or to use. */ + private Iterable<String> codes; + + /** + * Creates a new union of identifier codes and candidate codes. + * + * @param factory value of {@link IdentifiedObjectFinder#factory}. + * @param identifiers identifiers of the object to search. + * @param type type of objects to request for code candidates, or {@code null}. + */ - Codes(final AuthorityFactory factory, final Set<Identifier> identifiers, final Class<? extends IdentifiedObject> type) { ++ Codes(final AuthorityFactory factory, final Set<ReferenceIdentifier> identifiers, final Class<? extends IdentifiedObject> type) { + this.factory = factory; + this.identifiers = identifiers; + this.type = type; + } + + /** + * Invoked when the caller requested to stop the iteration after the current group of elements. + * A group of elements is either the codes specified by the identifiers, or the codes found in + * the database. We will avoid to stop in the middle of a group. + * + * <p>This is an undocumented feature of {@link #createFromCodes(IdentifiedObject)} + * for stopping an iteration early when at least one match has been found.</p> + */ + @Override + public void dispose() { + type = null; + } + + /** + * Converts the given identifier to a code returned by {@link #getIdentifiers()}. + * We accept only codes with a namespace (e.g. "AUTHORITY:CODE") for avoiding ambiguity. + * We do not try to check by ourselves if the identifier is in the namespace of the factory, + * because calling {@code factory.getAuthorityCodes()} or {@code factory.getCodeSpaces()} + * may be costly for some implementations. + */ + @Override - public String apply(final Identifier id) { ++ public String apply(final ReferenceIdentifier id) { + final String code = IdentifiedObjects.toString(id); + return (code.indexOf(Constants.DEFAULT_SEPARATOR) >= 0) ? code : null; + } + + /** + * Returns an iterator over codes of the identifiers of the object to search. + * The iteration does not include the {@code Identifier} of the name because, at least + * in Apache <abbr>SIS</abbr> implementations, the factories that accept object names + * already override {@link #getCodeCandidates(IdentifiedObject)} for including names. + */ + final Iterator<String> getIdentifiers() { + return new FilteredIterator<>(identifiers.iterator(), this); + } + + /** + * Returns an iterator over the code candidates. This method should be invoked only in last resort. + * + * @throws BackingStoreException if an error occurred while fetching the collection of authority codes. + */ + final Iterator<String> getAuthorityCodes() { + if (codes == null) { + if (type == null) { + codes = Set.of(); + } else try { + codes = factory.getAuthorityCodes(type); + } catch (FactoryException e) { + throw new BackingStoreException(e); + } + } + return codes.iterator(); + } + + /** + * Returns an iterator over the identifiers followed by the code candidates. + */ + @Override + public Iterator<String> iterator() { + return new Iterator<String>() { + /** The iterator of the code to return. */ + private Iterator<String> codes = getIdentifiers(); + + /** Whether {@link #codes} is for code candidates. */ + private boolean isCodeCandidates; + + /** Returns whether there is more codes to return. */ + @Override public boolean hasNext() { + if (!isCodeCandidates) { + if (codes.hasNext()) return true; + codes = getAuthorityCodes(); + isCodeCandidates = true; + } + return codes.hasNext(); + } + + /** Returns the next code. */ + @Override public String next() { + if (!(isCodeCandidates || codes.hasNext())) { + codes = getAuthorityCodes(); + } + return codes.next(); + } + }; + } } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java index 872885db81,1c049568bb..e54cb12614 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java @@@ -1846,10 -1782,45 +1848,45 @@@ public class MultiAuthoritiesFactory ex } /** - * Delegates to every factories registered in the enclosing {@link MultiAuthoritiesFactory}, - * in iteration order. This method is invoked only if the parent class failed to find the - * object by its identifiers and by its name. At this point, as a last resort, we will scan - * over the objects in the database. + * Creates objects equal (optionally ignoring metadata) to the specified object using the given identifiers. + * This method may be used in order to get a fully identified object from a partially identified one. + * + * @param type the type of the factory which is needed. + * @param object the user-specified object to search. + * @param identifiers {@code object.getIdentifiers()} or {@code object.getName()}. + * @param result the collection where to add the object instantiated from the identifiers. + * @throws FactoryException if an error occurred while creating an object. + */ + private void createFromIdentifiers(final AuthorityFactoryIdentifier.Type type, final IdentifiedObject object, - final Iterable<Identifier> identifiers, final Set<IdentifiedObject> result) throws FactoryException ++ final Iterable<ReferenceIdentifier> identifiers, final Set<IdentifiedObject> result) throws FactoryException + { - for (final Identifier identifier : identifiers) { ++ for (final ReferenceIdentifier identifier : identifiers) { + final String authority = identifier.getCodeSpace(); + if (authority != null) { + @SuppressWarnings("LocalVariableHidesMemberVariable") + final var factory = (MultiAuthoritiesFactory) this.factory; + final IdentifiedObject candidate; + try { + final var fid = AuthorityFactoryIdentifier.create(type, authority, identifier.getVersion()); + candidate = createAndFilter(factory.getAuthorityFactory(fid), identifier.getCode(), object); + } catch (NoSuchAuthorityCodeException e) { + // The identifier was not recognized. No problem, let's go on. + exceptionOccurred(e); + continue; + } + if (candidate != null) { + result.add(candidate); + } + } + } + } + + /** + * Creates objects approximately equal to the specified object by iterating over authority code candidates. + * This method is invoked by {@link #find(IdentifiedObject)} when the result was not already in the cache. + * First, this method tries to delegate to the factory specified by the name space of each identifier. + * If this quick search using declared identifiers did not worked, then this method delegates to every + * factories registered in the enclosing {@link MultiAuthoritiesFactory}, in iteration order. * * <p>This method shall <strong>not</strong> delegate the job to the parent class, as the default * implementation in the parent class is very inefficient. We need to delegate to the finders of diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java index b94cbce82e,5c95acd0bc..e113272552 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java @@@ -53,9 -64,13 +64,13 @@@ import org.apache.sis.referencing.facto import org.apache.sis.referencing.factory.ConcurrentAuthorityFactory; import static org.apache.sis.metadata.privy.NameToIdentifier.Simplifier.ESRI_DATUM_PREFIX; +// Specific to the main and geoapi-3.1 branches: +import org.opengis.referencing.crs.GeneralDerivedCRS; + + // Specific to the geoapi-3.1 and geoapi-4.0 branches: + import org.opengis.referencing.crs.ParametricCRS; + import org.opengis.referencing.datum.ParametricDatum; + -// Specific to the geoapi-4.0 branch: -import org.opengis.referencing.crs.DerivedCRS; - /** * An implementation of {@link IdentifiedObjectFinder} which scans over a smaller set of authority codes. @@@ -319,14 -361,18 +361,18 @@@ crs: if (isInstance(CoordinateRefere * ORDER BY COORD_REF_SYS_CODE */ final Condition filter; - if (object instanceof DerivedCRS) { // No need to use isInstance(Class, Object) from here. - filter = dependencies("BASE_CRS_CODE", SingleCRS.class, ((DerivedCRS) object).getBaseCRS(), true); + if (object instanceof GeneralDerivedCRS) { // No need to use isInstance(Class, Object) from here. + filter = dependencies("BASE_CRS_CODE", CoordinateReferenceSystem.class, ((GeneralDerivedCRS) object).getBaseCRS(), true); } else if (object instanceof GeodeticCRS) { - filter = dependencies("DATUM_CODE", GeodeticDatum.class, ((GeodeticCRS) object).getDatum(), true); + filter = dependencies("DATUM_CODE", GeodeticDatum.class, DatumOrEnsemble.asDatum((GeodeticCRS) object), true); } else if (object instanceof VerticalCRS) { - filter = dependencies("DATUM_CODE", VerticalDatum.class, ((VerticalCRS) object).getDatum(), true); + filter = dependencies("DATUM_CODE", VerticalDatum.class, DatumOrEnsemble.asDatum((VerticalCRS) object), true); } else if (object instanceof TemporalCRS) { - filter = dependencies("DATUM_CODE", TemporalDatum.class, ((TemporalCRS) object).getDatum(), true); + filter = dependencies("DATUM_CODE", TemporalDatum.class, DatumOrEnsemble.asDatum((TemporalCRS) object), true); + } else if (object instanceof ParametricCRS) { + filter = dependencies("DATUM_CODE", ParametricDatum.class, DatumOrEnsemble.asDatum((ParametricCRS) object), true); + } else if (object instanceof EngineeringCRS) { + filter = dependencies("DATUM_CODE", EngineeringDatum.class, DatumOrEnsemble.asDatum((EngineeringCRS) object), true); } else if (object instanceof SingleCRS) { filter = dependencies("DATUM_CODE", Datum.class, ((SingleCRS) object).getDatum(), true); } else { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java index 49c0195828,2622020443..835c89fb5e --- 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 @@@ -25,135 -26,196 +26,193 @@@ import org.opengis.referencing.datum.* import org.opengis.referencing.operation.*; import org.opengis.parameter.ParameterDescriptor; import org.apache.sis.referencing.privy.WKTKeywords; - import org.apache.sis.util.CharSequences; -// Specific to the geoapi-4.0 branch: -import org.apache.sis.referencing.crs.DefaultGeocentricCRS; - /** - * Information about a specific table. This class uses the mixed-case variant of the <abbr>EPSG</abbr> table names. - * If needed, those names will be converted by {@link SQLTranslator#apply(String)} to the actual table names. + * Information (such as columns of particular interest) about a specific <abbr>EPSG</abbr> table. + * This class uses the mixed-case variant of the <abbr>EPSG</abbr> {@linkplain #table table names}. + * The {@link #values()} can be tested in preference order for finding the table of an object. + * Those tables are used by the {@link EPSGDataAccess#createObject(String)} method in order to + * detect which of the following methods should be invoked for a given code: + * + * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)} + * {@link EPSGDataAccess#createCoordinateSystem(String)} + * {@link EPSGDataAccess#createDatum(String)} + * {@link EPSGDataAccess#createEllipsoid(String)} + * {@link EPSGDataAccess#createUnit(String)} + * + * <h4>Ambiguity</h4> + * As of ISO 19111:2019, we have no standard way to identify the geocentric case from a {@link Class} argument + * because the standard does not provide the {@code GeocentricCRS} interface. This implementation fallbacks on - * the <abbr>SIS</abbr>-specific geocentric <abbr>CRS</abbr> class. This special case is implemented in the ++ * the GeoAPI-specific geocentric <abbr>CRS</abbr> class. This special case is implemented in the + * {@link #appendWhere(EPSGDataAccess, Object, StringBuilder)} method. * * @author Martin Desruisseaux (IRD, Geomatys) */ - final class TableInfo { + enum TableInfo { /** - * The item used for coordinate reference systems. + * Information about the "Coordinate Reference System" table. */ - static final TableInfo CRS; + CRS(CoordinateReferenceSystem.class, + "\"Coordinate Reference System\"", + "COORD_REF_SYS_CODE", + "COORD_REF_SYS_NAME", + "COORD_REF_SYS_KIND", - new Class<?>[] { ProjectedCRS.class, GeographicCRS.class, DefaultGeocentricCRS.class, ++ new Class<?>[] { ProjectedCRS.class, GeographicCRS.class, GeocentricCRS.class, + VerticalCRS.class, CompoundCRS.class, EngineeringCRS.class, + DerivedCRS.class, TemporalCRS.class, ParametricCRS.class}, // See comment below + new String[] {"projected", "geographic 2D", "geocentric", // 3D case handled below + "vertical", "compound", "engineering", + "derived", "temporal", "parametric"}, // See comment below + "SHOW_CRS", true), + /* + * Above declaration could omit Derived, Temporal and Parametric cases because they are not defined + * by the EPSG repository (at least as of version 8.9). In particular we are not sure if EPSG would + * chose to use "time" or "temporal". However, omitting those types slow down a lot the search for + * CRS matching an existing one (even if it still work). + */ /** - * The item used for datums. + * Information about the "Datum" table. */ - static final TableInfo DATUM; + DATUM(Datum.class, + "\"Datum\"", + "DATUM_CODE", + "DATUM_NAME", + "DATUM_TYPE", + new Class<?>[] { DatumEnsemble.class, // Need to be first because Apache SIS uses as mixin interface. + GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class, + TemporalDatum.class, ParametricDatum.class}, + new String[] {"ensemble", + "geodetic", "vertical", "engineering", + "temporal", "parametric"}, // Same comment as in the CRS case above. + null, true), /** - * The item used for ellipsoids. + * Information about the "Conventional RS" table. + * This enumeration usually needs to be ignored because the current type is too generic. + * + * @see #isSpecificEnough() */ - static final TableInfo ELLIPSOID; + CONVENTIONAL_RS(IdentifiedObject.class, + "\"Conventional RS\"", + "CONVENTIONAL_RS_CODE", + "CONVENTIONAL_RS_NAME", + null, null, null, null, false), /** - * List of tables and columns to test for existence of codes values. - * Those tables are used by the {@link EPSGDataAccess#createObject(String)} method - * in order to detect which of the following methods should be invoked for a given code: - * - * {@link EPSGDataAccess#createCoordinateReferenceSystem(String)} - * {@link EPSGDataAccess#createCoordinateSystem(String)} - * {@link EPSGDataAccess#createDatum(String)} - * {@link EPSGDataAccess#createEllipsoid(String)} - * {@link EPSGDataAccess#createUnit(String)} + * Information about the "Ellipsoid" table. + */ + ELLIPSOID(Ellipsoid.class, + "\"Ellipsoid\"", + "ELLIPSOID_CODE", + "ELLIPSOID_NAME", + null, null, null, null, false), + + /** + * Information about the "Prime Meridian" table. + */ + PRIME_MERIDIAN(PrimeMeridian.class, + "\"Prime Meridian\"", + "PRIME_MERIDIAN_CODE", + "PRIME_MERIDIAN_NAME", + null, null, null, null, false), + + /** + * Information about the "Coordinate_Operation" table. + */ + OPERATION(CoordinateOperation.class, + "\"Coordinate_Operation\"", + "COORD_OP_CODE", + "COORD_OP_NAME", + "COORD_OP_TYPE", + new Class<?>[] { Conversion.class, Transformation.class}, + new String[] {"conversion", "transformation"}, + "SHOW_OPERATION", true), + + /** + * Information about the "Coordinate_Operation Method" table. + */ + METHOD(OperationMethod.class, + "\"Coordinate_Operation Method\"", + "COORD_OP_METHOD_CODE", + "COORD_OP_METHOD_NAME", + null, null, null, null, false), + + /** + * Information about the "Coordinate_Operation Parameter" table. + */ + PARAMETER(ParameterDescriptor.class, + "\"Coordinate_Operation Parameter\"", + "PARAMETER_CODE", + "PARAMETER_NAME", + null, null, null, null, false), + + /** + * Information about the "Extent" table. + */ + EXTENT(Extent.class, + "\"Extent\"", + "EXTENT_CODE", + "EXTENT_NAME", + null, null, null, null, false), + + /** + * Information about the "Coordinate System" table. + */ + CS(CoordinateSystem.class, + "\"Coordinate System\"", + "COORD_SYS_CODE", + "COORD_SYS_NAME", + "COORD_SYS_TYPE", + new Class<?>[] {CartesianCS.class, EllipsoidalCS.class, VerticalCS.class, LinearCS.class, + SphericalCS.class, PolarCS.class, CylindricalCS.class, + TimeCS.class, ParametricCS.class, AffineCS.class}, + new String[] {WKTKeywords.Cartesian, WKTKeywords.ellipsoidal, WKTKeywords.vertical, WKTKeywords.linear, + WKTKeywords.spherical, WKTKeywords.polar, WKTKeywords.cylindrical, + WKTKeywords.temporal, WKTKeywords.parametric, WKTKeywords.affine}, // Same comment as in the CRS case above. + null, false), + + /** + * Information about the "Coordinate Axis" table. + */ + AXIS(CoordinateSystemAxis.class, + "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis Name\" AS CAN " + + "ON CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE", + "COORD_AXIS_CODE", + "COORD_AXIS_NAME", + null, null, null, null, false), + + /** + * Information about the "Unit of Measure" table. + */ + UNIT(Unit.class, + "\"Unit of Measure\"", + "UOM_CODE", + "UNIT_OF_MEAS_NAME", + null, null, null, null, false); + + /** + * Types to consider as synonymous for searching purposes. This map exists for historical reasons, + * because dynamic datum and datum ensemble did not existed in older <abbr>ISO</abbr> 19111 standards. + * If an object to search is "geodetic", there is a possibility that it is defined in the old way and + * actually appears as a "dynamic geodetic" or "ensemble" in the <abbr>EPSG</abbr> geodetic dataset. * - * The order is significant: it is the key for a {@code switch} statement. + * <p>The "geographic 3D" case is handled in a special way. It is considered as a synonymous of + * "geographic 2D" only when we don't know the number of dimensions.</p> + */ + private static final Map<String, String[]> SYNONYMOUS_TYPES = Map.of( + "geodetic", new String[] {"dynamic geodetic", "ensemble"}, + "geographic 2D", new String[] {"geographic 3D"}); + + /** + * Types to replace by specialized types when the user-specified instance implements a mixin interface. + * For example, {@link DynamicReferenceFrame} means to not search for any geodetic datum, but only for + * dynamic geodetic datum. */ - @SuppressWarnings("deprecation") - static final TableInfo[] EPSG = { - CRS = new TableInfo(CoordinateReferenceSystem.class, - "\"Coordinate Reference System\"", - "COORD_REF_SYS_CODE", - "COORD_REF_SYS_NAME", - "COORD_REF_SYS_KIND", - new Class<?>[] { ProjectedCRS.class, GeographicCRS.class, GeocentricCRS.class, - VerticalCRS.class, CompoundCRS.class, EngineeringCRS.class, - DerivedCRS.class, TemporalCRS.class, ParametricCRS.class}, // See comment below - new String[] {"projected", "geographic", "geocentric", - "vertical", "compound", "engineering", - "derived", "temporal", "parametric"}, // See comment below - "SHOW_CRS", true), - /* - * Above declaration could omit Derived, Temporal and Parametric cases because they are not defined - * by the EPSG repository (at least as of version 8.9). In particular we are not sure if EPSG would - * chose to use "time" or "temporal". However, omitting those types slow down a lot the search for - * CRS matching an existing one (even if it still work). - */ - - new TableInfo(CoordinateSystem.class, - "\"Coordinate System\"", - "COORD_SYS_CODE", - "COORD_SYS_NAME", - "COORD_SYS_TYPE", - new Class<?>[] {CartesianCS.class, EllipsoidalCS.class, VerticalCS.class, LinearCS.class, - SphericalCS.class, PolarCS.class, CylindricalCS.class, - TimeCS.class, ParametricCS.class, AffineCS.class}, - new String[] {WKTKeywords.Cartesian, WKTKeywords.ellipsoidal, WKTKeywords.vertical, WKTKeywords.linear, - WKTKeywords.spherical, WKTKeywords.polar, WKTKeywords.cylindrical, - WKTKeywords.temporal, WKTKeywords.parametric, WKTKeywords.affine}, // Same comment as in the CRS case above. - null, false), - - new TableInfo(CoordinateSystemAxis.class, - "\"Coordinate Axis\" AS CA INNER JOIN \"Coordinate Axis Name\" AS CAN " + - "ON CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE", - "COORD_AXIS_CODE", - "COORD_AXIS_NAME", - null, null, null, null, false), - - DATUM = new TableInfo(Datum.class, - "\"Datum\"", - "DATUM_CODE", - "DATUM_NAME", - "DATUM_TYPE", - new Class<?>[] { GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class, - TemporalDatum.class, ParametricDatum.class}, - new String[] {"geodetic", "vertical", "engineering", - "temporal", "parametric"}, // Same comment as in the CRS case above. - null, true), - - ELLIPSOID = new TableInfo(Ellipsoid.class, - "\"Ellipsoid\"", - "ELLIPSOID_CODE", - "ELLIPSOID_NAME", - null, null, null, null, false), - - new TableInfo(PrimeMeridian.class, - "\"Prime Meridian\"", - "PRIME_MERIDIAN_CODE", - "PRIME_MERIDIAN_NAME", - null, null, null, null, false), - - new TableInfo(CoordinateOperation.class, - "\"Coordinate_Operation\"", - "COORD_OP_CODE", - "COORD_OP_NAME", - "COORD_OP_TYPE", - new Class<?>[] { Conversion.class, Transformation.class}, - new String[] {"conversion", "transformation"}, - "SHOW_OPERATION", true), - - new TableInfo(OperationMethod.class, - "\"Coordinate_Operation Method\"", - "COORD_OP_METHOD_CODE", - "COORD_OP_METHOD_NAME", - null, null, null, null, false), - - new TableInfo(ParameterDescriptor.class, - "\"Coordinate_Operation Parameter\"", - "PARAMETER_CODE", - "PARAMETER_NAME", - null, null, null, null, false), - - new TableInfo(Unit.class, - "\"Unit of Measure\"", - "UOM_CODE", - "UNIT_OF_MEAS_NAME", - null, null, null, null, false), - }; + private static final Map<String, String> DYNAMIC_TYPES = Map.of( + "geodetic", "dynamic geodetic"); + // We would expect a "dynamic vertical" as well, but we don't see it yet in EPSG database. /** * The class of object to be created (usually a GeoAPI interface). @@@ -287,22 -347,63 +344,63 @@@ } /** - * Appends a {@code WHERE} clause together with a condition for searching the most specific subtype, - * if such condition can be added. The clause appended by this method looks like the following example - * (details may vary because of enumeration values): + * Appends a {@code WHERE} clause together with a condition for searching the most specific subtype. + * The clause appended by this method looks like the following example: * * {@snippet lang="sql" : - * WHERE COORD_REF_SYS_KIND LIKE 'geographic%' AND + * WHERE (COORD_REF_SYS_KIND = 'geographic 2D' OR COORD_REF_SYS_KIND = 'geographic 3D') AND * } * - * The caller shall add at least one condition after this method call. + * The <abbr>SQL</abbr> fragment will have a trailing {@code WHERE} or {@code AND} keyword. + * Therefore, the caller shall add at least one condition after this method call. + * + * <h4>Object type</h4> + * The {@code object} argument shall be one of the following types: + * + * <ul> + * <li>An {@link IdentifiedObject} instance.</li> + * <li>The {@link Class} of an {@code IdentifiedObject}. It may be an implementation class.</li> + * <li>An opaque key computed by {@link #toCacheKey(IdentifiedObject)}.</li> + * </ul> * - * @param factory the factory which is writing a <abbr>SQL</abbr> statement. - * @param userType the type specified by the user. - * @param buffer where to append the {@code WHERE} clause. - * @return the subtype, or {@link #type} if no subtype was found. + * This method returns a generalization of the {@code object} argument: either a GeoAPI interface, + * or {@code object} if it was a cache key computed by {@link #toCacheKey(IdentifiedObject)}. + * + * @param factory the factory which is writing a <abbr>SQL</abbr> statement. + * @param object the instance, class or cache key to search in the database. + * @param buffer where to append the {@code WHERE} clause. + * @return the {@code object} argument, potentially generalized. */ - final Class<?> where(final EPSGDataAccess factory, final Class<?> userType, final StringBuilder buffer) { + final Object appendWhere(final EPSGDataAccess factory, final Object object, final StringBuilder buffer) { + final int dimension; // 0 if not applicable. This is applicable only to `GeographicCRS`. + final Class<?> userType; + if (object instanceof Integer) { + dimension = (Integer) object; + userType = GeographicCRS.class; + } else if (object instanceof Class<?>) { + userType = (Class<?>) object; + dimension = 0; + } else if (object instanceof GeodeticCRS) { + final CoordinateSystem cs = ((GeodeticCRS) object).getCoordinateSystem(); + if (cs instanceof EllipsoidalCS) { + userType = GeographicCRS.class; + dimension = cs.getDimension(); // Intentionally restricted to this specific case. + } else { + if (cs instanceof CartesianCS || cs instanceof SphericalCS) { - userType = DefaultGeocentricCRS.class; ++ userType = GeocentricCRS.class; + } else { + userType = object.getClass(); + } + dimension = 0; + } + } else { + userType = object.getClass(); + dimension = 0; + } + /* + * Above code decomposed the given `object`. + * The rest of this method builds the SQL. + */ buffer.append(" WHERE "); if (typeColumn != null) { for (int i=0; i<subTypes.length; i++) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java index 6b699e66f1,28ee86f03f..39919400ee --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java @@@ -250,8 -250,8 +250,8 @@@ detach: for (RTreeNode next; node != nu final CoordinateReferenceSystem crs = node.getCoordinateReferenceSystem(); if (common == null) { common = crs; - } else if (crs != null && !Utilities.equalsIgnoreMetadata(common, crs)) { + } else if (crs != null && !CRS.equivalent(common, crs)) { - throw new MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS)); + throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS)); } } } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java index 2911c9a540,88a6330dfa..e2aacdd5c1 --- 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 @@@ -148,8 -148,9 +148,11 @@@ public final class VerticalDatumTypes case ORTHOMETRIC: return 2001; // CS_VD_Orthometric case ELLIPSOIDAL: return 2002; // CS_VD_Ellipsoidal case BAROMETRIC: return 2003; // CS_VD_AltitudeBarometric - case "GEOIDAL": return 2005; // CS_VD_GeoidModelDerived ++ case "GEOIDAL": + case "GEOID": return 2005; // CS_VD_GeoidModelDerived + case "LEVELLING": // From ISO: "adjustment of a levelling network fixed to one or more tide gauges". - case "TIDAL": return 2006; // CS_VD_Depth ++ case "TIDAL": + case "DEPTH": return 2006; // CS_VD_Depth } } return 2000; diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java index 854470625e,3c45c52ffc..080993c5d5 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java @@@ -328,17 -324,11 +328,16 @@@ class CoordinateOperationRegistry } codes = new ArrayList<>(); codeFinder.setIgnoringAxes(true); - codeFinder.setSearchDomain(isEasySearch(crs) - ? IdentifiedObjectFinder.Domain.EXHAUSTIVE_VALID_DATASET - : IdentifiedObjectFinder.Domain.VALID_DATASET); + codeFinder.setSearchDomain(isEasySearch(crs) ? IdentifiedObjectFinder.Domain.EXHAUSTIVE_VALID_DATASET + : IdentifiedObjectFinder.Domain.VALID_DATASET); int matchCount = 0; try { - final Citation authority = registry.getAuthority(); + final Citation authority; + try { + authority = registry.getAuthority(); + } catch (BackingStoreException e) { + throw e.unwrapOrRethrow(FactoryException.class); + } for (final IdentifiedObject candidate : codeFinder.find(crs)) { final Identifier identifier = IdentifiedObjects.getIdentifier(candidate, authority); if (identifier != null) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java index 05f765e5cb,8a202cff50..77a1abd1b3 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java @@@ -487,10 -446,8 +485,10 @@@ public class DefaultOperationMethod ext break; } } - final OperationMethod that = (OperationMethod) object; + final var that = (OperationMethod) object; - return Utilities.deepEquals(getParameters(), that.getParameters(), mode); + return Objects.equals(getSourceDimensions(), that.getSourceDimensions()) && + Objects.equals(getTargetDimensions(), that.getTargetDimensions()) && + Utilities.deepEquals(getParameters(), that.getParameters(), mode); } return false; } diff --cc endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java index ed337dad42,88c1a98f29..90f79f248a --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java @@@ -29,9 -32,9 +32,9 @@@ import org.opengis.referencing.operatio * <li>{@link #STRICT} – All attributes of the compared objects shall be strictly equal.</li> * <li>{@link #BY_CONTRACT} – Only the attributes published in the interface contract need to be compared.</li> * <li>{@link #IGNORE_METADATA} – Only the attributes relevant to the object functionality are compared.</li> - * <li>{@link #APPROXIMATE} – Only the attributes relevant to the object functionality are compared, - * with some tolerance threshold on numerical values.</li> - * <li>{@link #ALLOW_VARIANT} – For objects not really equal but related (e.g. CRS using different axis order).</li> - * <li>{@link #COMPATIBILITY} – Like {@code IGNORE_METADATA}, but ignore also some structural changes for historical reasons.</li> ++ * <li>{@link #COMPATIBILITY} – Like {@code IGNORE_METADATA}, but ignore also some structural changes for historical reasons.</li> + * <li>{@link #APPROXIMATE} – Like {@code COMPATIBILITY}, with some tolerance threshold on numerical values.</li> + * <li>{@link #ALLOW_VARIANT} – Objects not really equal but related (e.g., <abbr>CRS</abbr> using different axis order).</li> * <li>{@link #DEBUG} – Special mode for figuring out why two objects expected to be equal are not.</li> * </ol> *
