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 a4a443d79e03313f59b1208ea658d18656630b4f Merge: 0661f9eaf0 4e0bc740bc Author: Martin Desruisseaux <[email protected]> AuthorDate: Mon Sep 1 12:13:16 2025 +0200 Merge branch 'geoapi-3.1'. This work improves the support of EPSG version 12. The handling of datum and CRS types in `TableInfo` has been improved. The search of EPSG code for an existing object has been improved. .../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 +- .../sis/geometry/wrapper/GeometryWrapper.java | 4 +- .../geometry/wrapper/SpatialOperationContext.java | 9 +- .../org/apache/sis/geometry/wrapper/jts/JTS.java | 5 +- .../coverage/grid/DimensionalityReductionTest.java | 2 +- .../org.apache.sis.metadata/main/module-info.java | 4 + .../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 +- .../sis/pending/geoapi/geometry/Geometry.java} | 28 +- .../sis/pending/geoapi/geometry/package-info.java} | 34 +- .../main/org/apache/sis/temporal/TemporalDate.java | 2 +- .../sis/metadata/sql/privy/SQLUtilitiesTest.java | 4 +- .../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 | 75 ++- .../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 | 109 ++-- .../sis/referencing/crs/AbstractDerivedCRS.java | 4 +- .../sis/referencing/crs/AbstractSingleCRS.java | 56 +- .../sis/referencing/crs/DefaultCompoundCRS.java | 50 +- .../sis/referencing/crs/DefaultDerivedCRS.java | 49 +- .../sis/referencing/crs/DefaultEngineeringCRS.java | 10 + .../sis/referencing/crs/DefaultGeodeticCRS.java | 16 +- .../sis/referencing/crs/DefaultParametricCRS.java | 10 + .../sis/referencing/crs/DefaultProjectedCRS.java | 21 +- .../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 | 244 ++++++-- .../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 | 67 +- .../factory/AuthorityFactoryIdentifier.java | 19 +- .../referencing/factory/AuthorityFactoryProxy.java | 3 + .../factory/ConcurrentAuthorityFactory.java | 130 +++- .../factory/GeodeticAuthorityFactory.java | 2 +- .../factory/IdentifiedObjectFinder.java | 686 +++++++++++++-------- .../factory/MultiAuthoritiesFactory.java | 113 +++- .../referencing/factory/sql/AuthorityCodes.java | 177 ++++-- .../factory/sql/CloseableReference.java | 6 + .../referencing/factory/sql/EPSGCodeFinder.java | 396 ++++++++++-- .../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 | 21 +- .../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/IdentifiedObjectFinderTest.java | 3 +- .../factory/MultiAuthoritiesFactoryTest.java | 2 +- .../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 | 36 -- .../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 +- 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 +- 167 files changed, 3573 insertions(+), 2217 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java index 127f829658,0ef0e3126c..0cb39b8a58 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/GeometryWrapper.java @@@ -24,7 -24,7 +24,6 @@@ import javax.measure.Unit import javax.measure.Quantity; import javax.measure.IncommensurableException; import javax.measure.quantity.Length; --import org.opengis.geometry.Geometry; import org.opengis.geometry.DirectPosition; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperation; @@@ -43,9 -43,14 +42,10 @@@ import org.apache.sis.util.Unconvertibl import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.resources.Errors; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import java.util.Set; -import org.opengis.geometry.Boundary; -import org.opengis.geometry.TransfiniteSet; -import org.opengis.geometry.complex.Complex; -import org.opengis.filter.SpatialOperatorName; -import org.opengis.filter.DistanceOperatorName; -import org.opengis.coordinate.MismatchedDimensionException; +// Specific to the main branch: ++import org.apache.sis.pending.geoapi.geometry.Geometry; +import org.apache.sis.pending.geoapi.filter.SpatialOperatorName; +import org.apache.sis.pending.geoapi.filter.DistanceOperatorName; /** diff --cc endorsed/src/org.apache.sis.metadata/main/module-info.java index 48064df3d8,d160fb9e63..b03e011efd --- a/endorsed/src/org.apache.sis.metadata/main/module-info.java +++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java @@@ -76,9 -76,6 +76,13 @@@ module org.apache.sis.metadata /* * Internal API open only to other Apache SIS modules. */ ++ exports org.apache.sis.pending.geoapi.geometry to ++ org.apache.sis.referencing, ++ org.apache.sis.feature; ++ + exports org.apache.sis.pending.geoapi.temporal to + org.apache.sis.feature; + exports org.apache.sis.temporal to org.apache.sis.referencing, org.apache.sis.feature, diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/Geometry.java index e3b30f74d9,ffe6f48b2c..3c29399d9e --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/Geometry.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/Geometry.java @@@ -14,33 -14,27 +14,27 @@@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.sis.referencing.factory.sql; -package org.apache.sis.geometries.operation.spatialedition; ++package org.apache.sis.pending.geoapi.geometry; - // Test dependencies - import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; - import org.apache.sis.test.TestCase; -import org.apache.sis.geometries.Geometry; -import org.apache.sis.geometries.operation.Operation; ++import org.opengis.geometry.Envelope; ++import org.opengis.referencing.crs.CoordinateReferenceSystem; /** - * Tests {@link TableInfo}. - * Transform geometry to a Primitive or MultiPrimitive. ++ * Placeholder for a GeoAPI interfaces incomplete in GeoAPI 3.0. * - * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) ++ * @since 1.5 ++ * @version 1.5 */ - public final class TableInfoTest extends TestCase { -public class ToPrimitive extends Operation<ToPrimitive> { - - public Geometry result; - ++public interface Geometry extends org.opengis.geometry.Geometry { /** - * Creates a new test case. - * - * @param geom geometry to transform ++ * Returns the coordinate reference system. */ - public TableInfoTest() { - public ToPrimitive(Geometry geom) { - super(geom); -- } ++ CoordinateReferenceSystem getCoordinateReferenceSystem(); + /** - * Tests {@link TableInfo#getObjectClassName(String)}. ++ * Returns the minimum bounding box for this {@code Geometry}. + */ - @Test - public void testgGetObjectClassName() { - assertEquals("Datum", TableInfo.getObjectClassName("epsg_datum").orElseThrow()); - assertEquals("Ellipsoid", TableInfo.getObjectClassName("epsg_ellipsoid").orElseThrow()); - assertEquals("CoordinateReferenceSystem", TableInfo.getObjectClassName("epsg_coordinatereferencesystem").orElseThrow()); - } ++ Envelope getEnvelope(); } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/package-info.java index e3b30f74d9,4307e9c627..e018f1dd2c --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/package-info.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/geometry/package-info.java @@@ -14,33 -14,14 +14,17 @@@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.sis.referencing.factory.sql; - - // Test dependencies - import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; - import org.apache.sis.test.TestCase; - /** - * Tests {@link TableInfo}. - * CQL parser. ++ * Placeholder for GeoAPI interfaces not present in GeoAPI 3.0. + * - * @author Johann Sorel (Geomatys) ++ * <STRONG>Do not use!</STRONG> ++ * ++ * This package is for internal use by SIS only. Classes in this package ++ * may change in incompatible ways in any future version without notice. + * + * @author Martin Desruisseaux (Geomatys) ++ * @since 0.3 ++ * @version 1.5 */ - public final class TableInfoTest extends TestCase { - /** - * Creates a new test case. - */ - public TableInfoTest() { - } - - /** - * Tests {@link TableInfo#getObjectClassName(String)}. - */ - @Test - public void testgGetObjectClassName() { - assertEquals("Datum", TableInfo.getObjectClassName("epsg_datum").orElseThrow()); - assertEquals("Ellipsoid", TableInfo.getObjectClassName("epsg_ellipsoid").orElseThrow()); - assertEquals("CoordinateReferenceSystem", TableInfo.getObjectClassName("epsg_coordinatereferencesystem").orElseThrow()); - } -module org.apache.sis.cql { - requires transitive org.apache.sis.feature; - requires org.locationtech.jts; - requires org.antlr.antlr4.runtime; --} ++package org.apache.sis.pending.geoapi.geometry; diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java index 8d53bf50e5,9624762637..ab836d7d7b --- 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 @@@ -204,8 -110,8 +204,8 @@@ public abstract class AbstractDirectPos * {@code null}, then all coordinate values are set to {@link Double#NaN NaN}. * * <p>If this position and the given position have a non-null CRS, then the default implementation - * requires the CRS to be {@linkplain Utilities#equalsIgnoreMetadata equals (ignoring metadata)}, + * requires the CRS to be {@linkplain CRS#equivalent equivalent}, - * otherwise a {@code MismatchedCoordinateMetadataException} is thrown. However, subclass may choose + * otherwise a {@code MismatchedReferenceSystemException} is thrown. However, subclass may choose * to assign the CRS of this position to the CRS of the given position.</p> * * @param position the new position, or {@code null}. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java index 4e1266cee8,3ebf31d761..8631f90798 --- 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 @@@ -699,7 -689,9 +700,9 @@@ class GeodeticObjectParser extends Math * @throws FactoryException if the factory cannot create the coordinate system. */ private CoordinateSystem parseCoordinateSystem(final Element parent, String type, int dimension, - final boolean isWKT1, final Unit<?> defaultUnit, final Datum datum) throws ParseException, FactoryException + final boolean isWKT1, final Unit<?> defaultUnit, - final boolean geodetic, final RealizationMethod vertical) ++ final boolean geodetic, final VerticalDatumType vertical) + throws ParseException, FactoryException { axisOrder.clear(); final boolean is3D = (dimension >= 3); @@@ -839,20 -831,17 +842,17 @@@ z = "h"; nz = "Height"; direction = AxisDirection.UP; - if (datum instanceof VerticalDatum) { - final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType(); - if (vt == VerticalDatumType.GEOIDAL) { - nz = AxisNames.GRAVITY_RELATED_HEIGHT; - z = "H"; - } else if (vt == VerticalDatumType.DEPTH) { - direction = AxisDirection.DOWN; - nz = AxisNames.DEPTH; - z = "D"; - } else if (VerticalDatumTypes.ellipsoidal(vt)) { - // Not allowed by ISO 19111 as a standalone axis, but SIS is - // tolerant to this case since it is sometimes hard to avoid. - nz = AxisNames.ELLIPSOIDAL_HEIGHT; - } - if (vertical == RealizationMethod.GEOID) { ++ if (vertical == VerticalDatumType.GEOIDAL) { + nz = AxisNames.GRAVITY_RELATED_HEIGHT; + z = "H"; - } else if (vertical == RealizationMethod.TIDAL) { ++ } else if (vertical == VerticalDatumType.DEPTH) { + direction = AxisDirection.DOWN; + nz = AxisNames.DEPTH; + z = "D"; + } else if (VerticalDatumTypes.ellipsoidal(vertical)) { + // Not allowed by ISO 19111 as a standalone axis, but SIS is + // tolerant to this case since it is sometimes hard to avoid. + nz = AxisNames.ELLIPSOIDAL_HEIGHT; } break; } @@@ -1939,7 -1928,7 +1940,7 @@@ } final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, datum); - cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, false, datum.getRealizationMethod().orElse(null)); ++ cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, false, datum.getVerticalDatumType()); final Map<String,?> properties = parseMetadataAndClose(element, name, datum); if (cs instanceof VerticalCS) { final CRSFactory crsFactory = factories.getCRSFactory(); @@@ -2081,9 -2071,9 +2082,9 @@@ } final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, datum); + cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, false, null); final Map<String,?> properties = parseMetadataAndClose(element, name, datum); - if (cs instanceof ParametricCS) { + if (cs != null) { final CRSFactory crsFactory = factories.getCRSFactory(); if (baseCRS != null) { return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java index f62d17c438,20250ab122..27dc8e2409 --- 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 @@@ -806,18 -787,11 +808,11 @@@ public class AbstractIdentifiedObject e return deepEquals(getName(), that.getName(), mode) && deepEquals(getAlias(), that.getAlias(), mode) && deepEquals(getIdentifiers(), that.getIdentifiers(), mode) && - deepEquals(getDomains(), that.getDomains(), mode) && + deepEquals(getDomains(), Legacy.getDomains(that), mode) && deepEquals(getRemarks(), that.getRemarks(), mode); } - case IGNORE_METADATA: - case APPROXIMATE: - case ALLOW_VARIANT: - case DEBUG: { - return implementsSameInterface(object); - } default: { - throw new IllegalArgumentException(Errors.format( - Errors.Keys.UnknownEnumValue_2, ComparisonMode.class, mode)); + return implementsSameInterface(object); } } } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java index c438930a46,21ffa968fd..3ba6f78225 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java @@@ -27,6 -27,6 +27,7 @@@ import java.util.logging.LogRecord import java.time.temporal.Temporal; import org.opengis.util.FactoryException; import org.opengis.geometry.Envelope; ++import org.opengis.geometry.Geometry; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.cs.CartesianCS; @@@ -51,6 -51,6 +52,8 @@@ import org.opengis.referencing.operatio import org.opengis.referencing.operation.TransformException; import org.opengis.metadata.citation.Citation; import org.opengis.metadata.extent.Extent; ++import org.opengis.metadata.extent.BoundingPolygon; ++import org.opengis.metadata.extent.GeographicExtent; import org.opengis.metadata.extent.GeographicBoundingBox; import org.apache.sis.measure.Units; import org.apache.sis.geometry.Envelopes; @@@ -96,11 -97,14 +100,13 @@@ import org.apache.sis.util.logging.Logg // 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.geometry.Geometry; -import org.opengis.referencing.ObjectDomain; -import org.opengis.referencing.crs.DerivedCRS; -import org.opengis.referencing.datum.DynamicReferenceFrame; -import org.opengis.metadata.extent.BoundingPolygon; -import org.opengis.metadata.extent.GeographicExtent; -import org.opengis.coordinate.CoordinateMetadata; +// Specific to the main branch: +import org.apache.sis.coordinate.DefaultCoordinateMetadata; ++import org.apache.sis.referencing.DefaultObjectDomain; ++import org.apache.sis.referencing.crs.AbstractCRS; +import org.apache.sis.referencing.datum.DefaultGeodeticDatum; +import org.apache.sis.referencing.datum.DefaultVerticalDatum; +import static org.apache.sis.pending.geoapi.referencing.MissingMethods.getDatumEnsemble; /** @@@ -877,7 -919,39 +911,40 @@@ public final class CRS extends Static public static Envelope getDomainOfValidity(final CoordinateReferenceSystem crs) { Envelope envelope = null; GeneralEnvelope merged = null; - if (crs != null) { - for (final ObjectDomain domain : crs.getDomains()) { ++ if (crs instanceof AbstractCRS) { ++ for (final DefaultObjectDomain domain : ((AbstractCRS) crs).getDomains()) { + final Extent domainOfValidity = domain.getDomainOfValidity(); + if (domainOfValidity != null) { + for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) { + if (extent instanceof BoundingPolygon && !Boolean.FALSE.equals(extent.getInclusion())) { + for (final Geometry geometry : ((BoundingPolygon) extent).getPolygons()) { - final Envelope candidate = geometry.getEnvelope(); ++ if (!(geometry instanceof org.apache.sis.pending.geoapi.geometry.Geometry)) continue; ++ final Envelope candidate = ((org.apache.sis.pending.geoapi.geometry.Geometry) geometry).getEnvelope(); + if (candidate != null) { + final CoordinateReferenceSystem sourceCRS = candidate.getCoordinateReferenceSystem(); + if (sourceCRS == null || equivalent(sourceCRS, crs)) { + if (envelope == null) { + envelope = candidate; + } else { + if (merged == null) { + envelope = merged = new GeneralEnvelope(envelope); + } + merged.add(envelope); + } + } + } + } + } + } + } + } + } + /* + * If no envelope was found, uses the geographic bounding box as a fallback. We will + * need to transform it from WGS84 to the supplied CRS. This step was not required in + * the previous block because the latter selected only envelopes in the right CRS. + */ - if (envelope == null) { + /* if (envelope == null) */ { // Condition needed on other branches but not on trunk. final GeographicBoundingBox bounds = getGeographicBoundingBox(crs); if (bounds != null && !Boolean.FALSE.equals(bounds.getInclusion())) { /* diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EPSGFactoryFallback.java index dab6091587,5d6f57441a..2a7f97c27c --- 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,9 -130,10 +130,9 @@@ 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); - @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); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java index 2decb802a2,67e0e6a6ae..32b4d34257 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java @@@ -272,7 -271,7 +272,7 @@@ public final class IdentifiedObjects ex if (authority instanceof IdentifierSpace<?>) { cs = ((IdentifierSpace<?>) authority).getName(); } - for (final ReferenceIdentifier identifier : nonNull(object.getIdentifiers())) { - for (final Identifier identifier : CollectionsExt.nonNull(object.getIdentifiers())) { ++ for (final ReferenceIdentifier identifier : CollectionsExt.nonNull(object.getIdentifiers())) { if (identifier != null) { // Paranoiac check. if (cs != null && cs.equalsIgnoreCase(identifier.getCodeSpace())) { return identifier; // Match based on codespace. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java index 3afcfb5a39,457b834f88..028ec16420 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java @@@ -299,25 -294,22 +297,48 @@@ public class AbstractCRS extends Abstra * User could provide his own CRS implementation outside this SIS package, so we have * to check for SingleCRS interface. But all SIS classes override this implementation. */ - return (this instanceof SingleCRS) ? ((SingleCRS) this).getDatum() : null; + if (this instanceof SingleCRS) { + final var crs = (SingleCRS) this; + final Datum datum = crs.getDatum(); + if (datum != null) { + return datum; + } + if (legacy) { - final var ensemble = crs.getDatumEnsemble(); ++ final var ensemble = getDatumEnsemble(crs); + if (ensemble instanceof Datum) { + return (Datum) ensemble; + } + } + } + return null; + } + ++ /** ++ * Returns the datum ensemble of the given <abbr>CRS</abbr>. ++ * ++ * @param crs the <abbr>CRS</abbr> from which to get the datum ensemble. ++ * @return the datum ensemble, or {@code null} if none. ++ */ ++ static DefaultDatumEnsemble<?> getDatumEnsemble(final CoordinateReferenceSystem crs) { ++ return (crs instanceof AbstractCRS) ? ((AbstractCRS) crs).getDatumEnsemble() : null; + } + + /** + * Returns the datum ensemble. + * + * @return the datum ensemble, or {@code null} if none. + */ + DefaultDatumEnsemble<?> getDatumEnsemble() { + return null; + } + + /** + * Initializes the handler for getting datum ensemble of an arbitrary CRS. + */ + static { - MissingMethods.datumEnsemble = (crs) -> (crs instanceof AbstractCRS) ? ((AbstractCRS) crs).getDatumEnsemble() : null; ++ MissingMethods.datumEnsemble = AbstractCRS::getDatumEnsemble; + } + /** * Returns the coordinate system. * diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java index 16172dd7aa,70690e8e25..d1276f17e8 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java @@@ -31,13 -31,12 +31,12 @@@ import org.apache.sis.util.resources.Er import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.cs.AbstractCS; import org.apache.sis.referencing.internal.Resources; - import org.apache.sis.metadata.privy.Identifiers; + import org.apache.sis.referencing.datum.DatumOrEnsemble; import org.apache.sis.metadata.privy.ImplementationHelper; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DatumEnsemble; -import org.opengis.metadata.Identifier; +// Specific to the main branch: +import org.opengis.referencing.ReferenceIdentifier; +import org.apache.sis.referencing.datum.DefaultDatumEnsemble; - import org.apache.sis.pending.geoapi.referencing.MissingMethods; /** @@@ -170,11 -169,11 +169,11 @@@ class AbstractSingleCRS<D extends Datum AbstractSingleCRS(final SingleCRS crs) { super(crs); datum = (D) crs.getDatum(); - if (datum instanceof DatumEnsemble<?>) { + if (datum instanceof DefaultDatumEnsemble<?>) { throw new IllegalArgumentException( - Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "datum", DatumEnsemble.class)); + Errors.format(Errors.Keys.IllegalPropertyValueClass_2, "datum", DefaultDatumEnsemble.class)); } - ensemble = (DefaultDatumEnsemble<D>) MissingMethods.getDatumEnsemble(crs); - ensemble = (DatumEnsemble<D>) crs.getDatumEnsemble(); ++ ensemble = (DefaultDatumEnsemble<D>) getDatumEnsemble(crs); checkDatum(null); } @@@ -238,25 -221,24 +221,24 @@@ @Override public boolean equals(final Object object, ComparisonMode mode) { if (super.equals(object, mode)) { - switch (mode) { - case STRICT: { - final var that = (AbstractSingleCRS<?>) object; - return Objects.equals(datum, that.datum) && Objects.equals(ensemble, that.ensemble); - } - default: { - final var that = (SingleCRS) object; - final var d1 = this.getDatum(); - final var d2 = that.getDatum(); - if (mode == ComparisonMode.DEBUG) { - mode = ComparisonMode.ALLOW_VARIANT; // For avoiding too early `AssertionError`. - } - if (Utilities.deepEquals(d1, d2, mode)) { - return mode.allowsVariant() || Utilities.deepEquals(getDatumEnsemble(), MissingMethods.getDatumEnsemble(that), mode); - } else if (mode.allowsVariant()) { - return isHeuristicMatchForName(this.getDatumEnsemble(), d2, mode) || - isHeuristicMatchForName(MissingMethods.getDatumEnsemble(that), d1, mode); - } + if (mode == ComparisonMode.STRICT) { + final var that = (AbstractSingleCRS<?>) object; + return Objects.equals(datum, that.datum) && Objects.equals(ensemble, that.ensemble); + } + final var that = (SingleCRS) object; + final var d1 = this.getDatum(); + final var d2 = that.getDatum(); + if (mode == ComparisonMode.DEBUG) { + mode = ComparisonMode.ALLOW_VARIANT; // For avoiding too early `AssertionError`. + } + if (Utilities.deepEquals(d1, d2, mode)) { + if (d1 != null && d2 != null && mode.isCompatibility()) { + return true; } - return Utilities.deepEquals(getDatumEnsemble(), that.getDatumEnsemble(), mode); ++ return Utilities.deepEquals(getDatumEnsemble(), getDatumEnsemble(that), mode); + } else if (mode.isCompatibility()) { + return DatumOrEnsemble.isLegacyDatum(this.getDatumEnsemble(), d2, mode) || - DatumOrEnsemble.isLegacyDatum(that.getDatumEnsemble(), d1, mode); ++ DatumOrEnsemble.isLegacyDatum(getDatumEnsemble(that), d1, mode); } } return false; diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java index 657427192c,714f98611d..40f51712e6 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java @@@ -59,12 -60,12 +60,11 @@@ import org.apache.sis.io.wkt.Formatter import org.apache.sis.util.ComparisonMode; import org.apache.sis.util.collection.Containers; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DatumEnsemble; -import org.opengis.referencing.datum.ParametricDatum; -import org.opengis.referencing.crs.ParametricCRS; -import org.opengis.referencing.cs.ParametricCS; -import org.opengis.coordinate.MismatchedDimensionException; +// Specific to the main branch: +import org.opengis.geometry.MismatchedDimensionException; +import org.apache.sis.referencing.cs.DefaultParametricCS; +import org.apache.sis.referencing.datum.DefaultDatumEnsemble; +import org.apache.sis.referencing.datum.DefaultParametricDatum; - import org.apache.sis.pending.geoapi.referencing.MissingMethods; /** @@@ -429,8 -430,20 +429,20 @@@ public class DefaultDerivedCRS extends * @since 1.5 */ @Override - public DatumEnsemble<?> getDatumEnsemble() { - return getBaseCRS().getDatumEnsemble(); + public DefaultDatumEnsemble<?> getDatumEnsemble() { - return MissingMethods.getDatumEnsemble(getBaseCRS()); ++ return getDatumEnsemble(getBaseCRS()); + } + + /** + * Returns the datum or a view of the ensemble as a datum. + */ + @Override + Datum getDatumOrEnsemble(final boolean legacy) { + final SingleCRS baseCRS = getBaseCRS(); + if (baseCRS instanceof AbstractCRS) { + return ((AbstractCRS) baseCRS).getDatumOrEnsemble(legacy); + } + return super.getDatumOrEnsemble(legacy); } /** @@@ -681,6 -692,16 +691,11 @@@ return (GeodeticDatum) super.getDatum(); } - /** Returns the datum ensemble of the base geodetic CRS. */ - @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble(); - } - + /** Returns the datum or a view of the ensemble as a datum. */ + @Override GeodeticDatum getDatumOrEnsemble(final boolean legacy) { + return legacy ? DatumOrEnsemble.asDatum((GeodeticCRS) getBaseCRS()) : getDatum(); + } + /** Returns a coordinate reference system of the same type as this CRS but with different axes. */ @Override AbstractCRS createSameType(final AbstractCS derivedCS) { return new Geodetic(this, derivedCS); @@@ -732,6 -753,16 +747,11 @@@ return (VerticalDatum) super.getDatum(); } - /** Returns the datum ensemble of the base vertical CRS. */ - @Override public DatumEnsemble<VerticalDatum> getDatumEnsemble() { - return ((VerticalCRS) getBaseCRS()).getDatumEnsemble(); - } - + /** Returns the datum or a view of the ensemble as a datum. */ + @Override VerticalDatum getDatumOrEnsemble(final boolean legacy) { + return legacy ? DatumOrEnsemble.asDatum((VerticalCRS) getBaseCRS()) : getDatum(); + } + /** Returns the coordinate system given at construction time. */ @Override public VerticalCS getCoordinateSystem() { return (VerticalCS) super.getCoordinateSystem(); @@@ -788,6 -819,16 +808,11 @@@ return (TemporalDatum) super.getDatum(); } - /** Returns the datum ensemble of the base temporal CRS. */ - @Override public DatumEnsemble<TemporalDatum> getDatumEnsemble() { - return ((TemporalCRS) getBaseCRS()).getDatumEnsemble(); - } - + /** Returns the datum or a view of the ensemble as a datum. */ + @Override TemporalDatum getDatumOrEnsemble(final boolean legacy) { + return legacy ? DatumOrEnsemble.asDatum((TemporalCRS) getBaseCRS()) : getDatum(); + } + /** Returns the coordinate system given at construction time. */ @Override public TimeCS getCoordinateSystem() { return (TimeCS) super.getCoordinateSystem(); @@@ -840,13 -881,23 +865,18 @@@ } /** Returns the datum of the base parametric CRS. */ - @Override public ParametricDatum getDatum() { - return (ParametricDatum) super.getDatum(); - } - - /** Returns the datum ensemble of the base parametric CRS. */ - @Override public DatumEnsemble<ParametricDatum> getDatumEnsemble() { - return ((ParametricCRS) getBaseCRS()).getDatumEnsemble(); + @Override public DefaultParametricDatum getDatum() { + return (DefaultParametricDatum) super.getDatum(); } + /** Returns the datum or a view of the ensemble as a datum. */ - @Override ParametricDatum getDatumOrEnsemble(final boolean legacy) { - return legacy ? DatumOrEnsemble.asDatum((ParametricCRS) getBaseCRS()) : getDatum(); ++ @Override DefaultParametricDatum getDatumOrEnsemble(final boolean legacy) { ++ return getDatum(); + } + /** Returns the coordinate system given at construction time. */ - @Override public ParametricCS getCoordinateSystem() { - return (ParametricCS) super.getCoordinateSystem(); + @Override public DefaultParametricCS getCoordinateSystem() { + return (DefaultParametricCS) super.getCoordinateSystem(); } /** Returns a coordinate reference system of the same type as this CRS but with different axes. */ @@@ -903,6 -954,16 +933,11 @@@ return (EngineeringDatum) super.getDatum(); } - /** Returns the datum ensemble of the base engineering CRS. */ - @Override public DatumEnsemble<EngineeringDatum> getDatumEnsemble() { - return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble(); - } - + /** Returns the datum or a view of the ensemble as a datum. */ + @Override EngineeringDatum getDatumOrEnsemble(final boolean legacy) { + return legacy ? DatumOrEnsemble.asDatum((EngineeringCRS) getBaseCRS()) : getDatum(); + } + /** Returns a coordinate reference system of the same type as this CRS but with different axes. */ @Override AbstractCRS createSameType(final AbstractCS derivedCS) { return new Engineering(this, derivedCS); diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java index 06f36fbe55,04ef599736..cf2b16bdf0 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java @@@ -160,14 -160,15 +161,23 @@@ class DefaultGeodeticCRS extends Abstra return super.getDatum(); } + /** + * Initializes the handler for getting datum ensemble of an arbitrary CRS. + */ + static { + MissingMethods.geodeticDatumEnsemble = (crs) -> + (crs instanceof DefaultGeodeticCRS) ? ((DefaultGeodeticCRS) crs).getDatumEnsemble() : null; + } + + /** + * Returns the datum or a view of the ensemble as a datum. + * The {@code legacy} argument tells whether this method is invoked for formatting in a legacy <abbr>WKT</abbr> format. + */ + @Override + final GeodeticDatum getDatumOrEnsemble(final boolean legacy) { + return legacy ? DatumOrEnsemble.asDatum(this) : getDatum(); + } + /** * Returns a coordinate reference system of the same type as this CRS but with different axes. * This method shall be overridden by all {@code DefaultGeodeticCRS} subclasses in this package. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java index f637341b31,5fedc4597a..efb669c751 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java @@@ -23,12 -23,14 +23,13 @@@ import jakarta.xml.bind.annotation.XmlT import org.apache.sis.referencing.privy.WKTKeywords; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.AbstractCS; + import org.apache.sis.referencing.datum.DatumOrEnsemble; import org.apache.sis.io.wkt.Formatter; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.cs.ParametricCS; -import org.opengis.referencing.crs.ParametricCRS; -import org.opengis.referencing.datum.ParametricDatum; -import org.opengis.referencing.datum.DatumEnsemble; +// Specific to the main branch: +import org.apache.sis.referencing.cs.DefaultParametricCS; +import org.apache.sis.referencing.datum.DefaultParametricDatum; +import org.apache.sis.referencing.datum.DefaultDatumEnsemble; /** @@@ -193,6 -219,15 +194,15 @@@ public class DefaultParametricCRS exten return super.getDatumEnsemble(); } + /** + * Returns the datum or a view of the ensemble as a datum. + * The {@code legacy} argument tells whether this method is invoked for formatting in a legacy <abbr>WKT</abbr> format. + */ + @Override - final ParametricDatum getDatumOrEnsemble(final boolean legacy) { - return legacy ? DatumOrEnsemble.asDatum(this) : getDatum(); ++ final DefaultParametricDatum getDatumOrEnsemble(final boolean legacy) { ++ return getDatum(); + } + /** * Returns the coordinate system. * diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java index 346f14ec00,10036913d9..5aa77cc06b --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java @@@ -44,10 -45,9 +45,9 @@@ import org.apache.sis.util.Workaround import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.operation.Projection; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DatumEnsemble; -import org.opengis.coordinate.MismatchedDimensionException; +// Specific to the main branch: +import org.opengis.geometry.MismatchedDimensionException; +import org.apache.sis.referencing.datum.DefaultDatumEnsemble; - import org.apache.sis.pending.geoapi.referencing.MissingMethods; /** @@@ -247,8 -247,17 +247,17 @@@ public class DefaultProjectedCRS extend * @since 1.5 */ @Override - public DatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return getBaseCRS().getDatumEnsemble(); + public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return MissingMethods.getDatumEnsemble(getBaseCRS()); ++ return (DefaultDatumEnsemble<GeodeticDatum>) getDatumEnsemble(getBaseCRS()); + } + + /** + * Returns the datum or a view of the ensemble as a datum. + * The {@code legacy} argument tells whether this method is invoked for formatting in a legacy <abbr>WKT</abbr> format. + */ + @Override + final GeodeticDatum getDatumOrEnsemble(final boolean legacy) { + return legacy ? DatumOrEnsemble.asDatum(getBaseCRS()) : getDatum(); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java index 4e6d19103c,07991ac8b4..b8bb4f3b62 --- 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 @@@ -507,15 -434,15 +507,15 @@@ public class AbstractDatum extends Abst * and the {@linkplain #getDomains() domains}. * * <h4>Static versus dynamic datum</h4> - * If this datum implements the {@link DynamicReferenceFrame} interface, then the given object needs + * If this datum implements the {@code DynamicReferenceFrame} interface, then the given object needs - * to also implement that interfaces and provide the same reference epoch for being considered equal. + * to also implement that interface and provide the same reference epoch for being considered equal. - * Conversely, if this datum does not implement {@link DynamicReferenceFrame}, then the given object + * Conversely, if this datum does not implement {@code DynamicReferenceFrame}, then the given object * also need to <em>not</em> implement that interface for being considered equal. + * This condition is relaxed with {@link ComparisonMode#COMPATIBILITY} if the two reference frames have a common identifier + * or an {@linkplain org.apache.sis.referencing.IdentifiedObjects#isHeuristicMatchForName equivalent name}. * * @param object the object to compare to {@code this}. - * @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or - * {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only - * properties relevant to coordinate transformations. + * @param mode the strictness level of the comparison. * @return {@code true} if both objects are equal. */ @Override diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java index 9823622305,d08ce7f653..ce78a4c244 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DatumOrEnsemble.java @@@ -60,12 -67,34 +66,35 @@@ import org.apache.sis.util.logging.Logg * @version 1.5 * @since 1.5 */ +@SuppressWarnings("unchecked") // See `getDatumEnsemble(…)` public final class DatumOrEnsemble extends Static { /** - * Do not allow instantiation of this class. + * The {@value} keyword which sometime appear at the end of a datum ensemble name. */ - private DatumOrEnsemble() { + private static final String ENSEMBLE = "ensemble"; + + /** + * The datum of a <abbr>CRS</abbr> or specified by the user, or {@code null} if none. + */ + private final Datum datum; + + /** + * The ensemble of a <abbr>CRS</abbr> or specified by the user, or {@code null} if none. + */ - private final DatumEnsemble<?> ensemble; ++ private final DefaultDatumEnsemble<?> ensemble; + + /** + * Criterion for deciding if two properties should be considered equal. + */ + private final ComparisonMode mode; + + /** + * For internal usage only. The fact that we may create instances of this class is a hidden implementation details. + */ - private DatumOrEnsemble(final Datum datum, final DatumEnsemble<?> ensemble, final ComparisonMode mode) { ++ private DatumOrEnsemble(final Datum datum, final DefaultDatumEnsemble<?> ensemble, final ComparisonMode mode) { + this.datum = datum; + this.ensemble = ensemble; + this.mode = mode; } /** @@@ -279,18 -318,18 +308,18 @@@ */ @SuppressWarnings("unchecked") // Casts are safe because callers know the method signature of <D>. private static <C extends SingleCRS, D extends Datum, R extends IdentifiedObject> Optional<R> asTargetDatum( - final C source, final R sourceDatum, - final C target, final R targetDatum, + final C sourceCRS, final R sourceDatum, + final C targetCRS, final R targetDatum, - final Function<DatumEnsemble<D>, R> constructor) + final Function<DefaultDatumEnsemble<D>, R> constructor) { if (sourceDatum != null && Utilities.equalsIgnoreMetadata(sourceDatum, targetDatum)) { return Optional.of(targetDatum); } - DatumEnsemble<D> sourceEnsemble; - DatumEnsemble<D> targetEnsemble; - DatumEnsemble<D> selected; - if ((isMember(selected = targetEnsemble = (DatumEnsemble<D>) targetCRS.getDatumEnsemble(), sourceDatum)) || - (isMember(selected = sourceEnsemble = (DatumEnsemble<D>) sourceCRS.getDatumEnsemble(), targetDatum))) + DefaultDatumEnsemble<D> sourceEnsemble; + DefaultDatumEnsemble<D> targetEnsemble; + DefaultDatumEnsemble<D> selected; - if ((isMember(selected = targetEnsemble = (DefaultDatumEnsemble<D>) getDatumEnsemble(target), sourceDatum)) || - (isMember(selected = sourceEnsemble = (DefaultDatumEnsemble<D>) getDatumEnsemble(source), targetDatum))) ++ if ((isMember(selected = targetEnsemble = (DefaultDatumEnsemble<D>) getDatumEnsemble(targetCRS), sourceDatum)) || ++ (isMember(selected = sourceEnsemble = (DefaultDatumEnsemble<D>) getDatumEnsemble(sourceCRS), targetDatum))) { return Optional.of(constructor.apply(selected)); } @@@ -343,15 -382,84 +372,84 @@@ return false; } + /** + * Returns whether a legacy definition of a datum may be considered as equivalent to the given datum ensemble. + * This is {@code true} if all reference frames (both the specified datum and the ensemble members) have the + * same properties (ellipsoid and prime meridians in the geodetic case), and the datum and datum ensemble either + * have a common identifier or an {@linkplain IdentifiedObjects#isHeuristicMatchForName(IdentifiedObject, String) + * heuristic match of name}. + * + * <p>This method does not verify if the given datum is a member of the given ensemble. + * If the datum was a member, then the two objects would <em>not</em> be conceptually equal. + * We would rather have one object clearly identified as more accurate than the other.</p> + * + * <h4>Use case</h4> + * This method is for interoperability between the old and new definitions of <abbr>WGS</abbr> 1984 (<abbr>EPSG</abbr>:4326). + * Before <abbr>ISO</abbr> 19111:2019, the <i>datum ensemble</i> concept did not existed in the <abbr>OGC</abbr>/<abbr>ISO</abbr> standards + * and <abbr>WGS</abbr> 1984 was defined as a {@link Datum}. - * In recent standards, <abbr>WGS</abbr> 1984 is defined as a {@link DatumEnsemble}, but the old definition is still encountered. ++ * In recent standards, <abbr>WGS</abbr> 1984 is defined as a {@code DatumEnsemble}, but the old definition is still encountered. + * For example, a <abbr>CRS</abbr> may have been parsed from a <abbr title="Geographic Markup Language">GML</abbr> document, + * or from a <abbr title="Well-Known Text">WKT</abbr> 1 string, or from a <abbr>ISO</abbr> 19162:2015 string, <i>etc.</i> + * This method can be used for detecting such situations. + * While <abbr>WGS</abbr> 1984 is the main use case, this method can be used for any datum in the same situation. + * + * @param ensemble the datum ensemble, or {@code null}. + * @param datum the datum, or {@code null}. + * @param mode the criterion for comparing ellipsoids and prime meridians. + * @return whether the two objects could be considered as equal if the concept of datum ensemble did not existed. + */ - public static boolean isLegacyDatum(final DatumEnsemble<?> ensemble, final Datum datum, final ComparisonMode mode) { ++ public static boolean isLegacyDatum(final DefaultDatumEnsemble<?> ensemble, final Datum datum, final ComparisonMode mode) { + if (ensemble == null || datum == null) { + return false; + } + // Two null values are not considered equal because they are not of the same type. + if (ensemble == datum) { + return true; + } + final var c = new DatumOrEnsemble(datum, ensemble, mode); + if (!(c.isPropertyEqual(GeodeticDatum.class, GeodeticDatum::getEllipsoid, Objects::nonNull) && + c.isPropertyEqual(GeodeticDatum.class, GeodeticDatum::getPrimeMeridian, Objects::nonNull) && - c.isPropertyEqual(VerticalDatum.class, VerticalDatum::getRealizationMethod, Optional::isPresent))) ++ c.isPropertyEqual(VerticalDatum.class, VerticalDatum::getVerticalDatumType, Objects::nonNull))) + { + return false; + } + final Boolean match = Identifiers.hasCommonIdentifier(ensemble.getIdentifiers(), datum.getIdentifiers()); + if (match != null) { + return match; + } + /* + * We could not answer the question using identifiers. Try using the names. + * The primary name is likely to not match, because ensemble names in EPSG + * dataset often ends with "ensemble" while datum names often do not. But + * we are more interrested in the ensemble's aliases in the next line. + */ + if (IdentifiedObjects.isHeuristicMatchForName(ensemble, datum.getName().getCode())) { + return true; + } + /* + * Try to remove the "ensemble" prefix in the datum ensemble name and try again. + * This time, the comparison will also check `datum` aliases instead of `ensemble`. + */ + String name = ensemble.getName().getCode(); + if (name.endsWith(ENSEMBLE)) { + int i = name.length() - ENSEMBLE.length(); + if (i > (i = CharSequences.skipTrailingWhitespaces(name, 0, i))) { + name = name.substring(0, i); // Remove the "ensemble" suffix. + } + } + return IdentifiedObjects.isHeuristicMatchForName(datum, name); + } + /** * Returns the ellipsoid used by the given coordinate reference system. - * More specifically: + * This method searches in the following locations: * * <ul> * <li>If the given <abbr>CRS</abbr> is an instance of {@link SingleCRS} and its datum * is a {@link GeodeticDatum}, then this method returns the datum ellipsoid.</li> - * <li>Otherwise, if the given <abbr>CRS</abbr> is associated to a {@code DatumEnsemble} and all members - * of the ensemble have equal (ignoring metadata) ellipsoid, then returns that ellipsoid.</li> + * <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link SingleCRS}, is associated to a - * {@link DatumEnsemble}, and all members of the ensemble have equal (ignoring metadata) ellipsoid, ++ * {@code DatumEnsemble}, and all members of the ensemble have equal (ignoring metadata) ellipsoid, + * then returns that ellipsoid.</li> * <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link CompoundCRS}, then this method * searches recursively in each component until a geodetic reference frame is found.</li> * <li>Otherwise, this method returns an empty value.</li> @@@ -378,39 -484,98 +474,71 @@@ * @see org.apache.sis.referencing.CRS#getGreenwichLongitude(GeodeticCRS) */ public static Optional<PrimeMeridian> getPrimeMeridian(final CoordinateReferenceSystem crs) { - return getGeodeticProperty(crs, GeodeticDatum::getPrimeMeridian); + return Optional.ofNullable(getProperty(crs, GeodeticDatum.class, GeodeticDatum::getPrimeMeridian, Objects::nonNull)); } - /** - * Returns the realization method used by the given coordinate reference system. - * This method searches in the following locations: - * - * <ul> - * <li>If the given <abbr>CRS</abbr> is an instance of {@link SingleCRS} and its datum - * is a {@link VerticalDatum}, then this method returns the realization method.</li> - * <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link SingleCRS}, is associated to a - * {@link DatumEnsemble}, and all members of the ensemble have equal (ignoring metadata) realization - * methods, then returns that method.</li> - * <li>Otherwise, if the given <abbr>CRS</abbr> is an instance of {@link CompoundCRS}, then this method - * searches recursively in each component until a vertical reference frame is found.</li> - * <li>Otherwise, this method returns an empty value.</li> - * </ul> - * - * This method may return an empty value if a datum ensemble contains different realization methods. - * - * @param crs the coordinate reference system for which to get the realization method. - * @return the realization method, or an empty value if none or not equal for all members. - * - * @since 2.0 (temporary version number until this branch is released) - */ - public static Optional<RealizationMethod> getRealizationMethod(final CoordinateReferenceSystem crs) { - Optional<RealizationMethod> common = getProperty(crs, VerticalDatum.class, VerticalDatum::getRealizationMethod, Optional::isPresent); - return (common != null) ? common : Optional.empty(); - } - /** * Implementation of {@code getEllipsoid(CRS)} and {@code getPrimeMeridian(CRS)}. * - * @param <P> the type of object to get. - * @param crs the coordinate reference system for which to get the ellipsoid or prime meridian. - * @param getter the method to invoke on {@link GeodeticDatum} instances. - * @return the ellipsoid or prime meridian, or an empty value if none of inconsistent. + * @param <P> the type of property to get. + * @param <D> the type of datum expected by the given {@code getter}. + * @param crs the coordinate reference system for which to get the ellipsoid or prime meridian. + * @param getter the method to invoke on {@link Datum} instances for getting the property. + * @param nonNull test about whether a property value is non-null or present. + * @return the property value, or {@code null} if none or not equal for all members. */ - private static <P> Optional<P> getGeodeticProperty(final CoordinateReferenceSystem crs, final Function<GeodeticDatum, P> getter) { - single: if (crs instanceof SingleCRS) { + private static <P, D extends Datum> P getProperty(final CoordinateReferenceSystem crs, final Class<D> datumType, + final Function<D, P> getter, final Predicate<P> nonNull) + { + if (crs instanceof SingleCRS) { final var scrs = (SingleCRS) crs; final Datum datum = scrs.getDatum(); - if (datum instanceof GeodeticDatum) { - P property = getter.apply((GeodeticDatum) datum); + if (datumType.isInstance(datum)) { + @SuppressWarnings("unchecked") + P property = getter.apply((D) datum); + if (nonNull.test(property)) { + return property; + } + } - final var c = new DatumOrEnsemble(datum, scrs.getDatumEnsemble(), ComparisonMode.IGNORE_METADATA); ++ final var c = new DatumOrEnsemble(datum, getDatumEnsemble(scrs), ComparisonMode.IGNORE_METADATA); + return c.getEnsembleProperty(null, datumType, getter, nonNull); + } else if (crs instanceof CompoundCRS) { + for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) { + final P property = getProperty(c, datumType, getter, nonNull); if (property != null) { - return Optional.of(property); + return property; } } - final DefaultDatumEnsemble<?> ensemble = getDatumEnsemble(crs); - if (ensemble != null) { - P common = null; - for (Datum member : ensemble.getMembers()) { - if (member instanceof GeodeticDatum) { - final P property = getter.apply((GeodeticDatum) member); - if (property != null) { - if (common == null) { - common = property; - } else if (!Utilities.equalsIgnoreMetadata(property, common)) { - break single; - } + } + return null; + } + + /** + * Returns a property of ensemble member if it is the same for all members. + * Returns {@code null} if the value is absent or not equal for all members. + * If {@code common} is non-null, then this method take in account only the + * property values equal to {@code common} + * (i.e., it searches if the value is present). + * + * @param <P> the type of property to get. + * @param <D> the type of datum expected by the given {@code getter}. + * @param common if non-null, ignore all properties not equal to {@code common}. + * @param getter the method to invoke on {@link Datum} instances for getting the property. + * @param nonNull test about whether a property value is non-null or present. + * @return the property value, or {@code null} if none or not equal for all members. + */ + private <P, D extends Datum> P getEnsembleProperty(P common, final Class<D> datumType, final Function<D,P> getter, final Predicate<P> nonNull) { + final boolean searching = (common != null); + if (ensemble != null) { + for (Datum member : ensemble.getMembers()) { + if (datumType.isInstance(member)) { + @SuppressWarnings("unchecked") + final P property = getter.apply((D) member); + if (nonNull.test(property)) { + if (common == null) { + common = property; + } else if (Utilities.deepEquals(property, common, mode) == searching) { + return searching ? common : null; } } } @@@ -434,11 -612,18 +575,18 @@@ * @param object the object from which to get the ensemble accuracy, or {@code null}. * @return the datum ensemble accuracy if the given object is a datum ensemble. * @throws NullPointerException if the given object should provide an accuracy but didn't. + * + * @see org.apache.sis.referencing.CRS#getLinearAccuracy(CoordinateOperation) */ public static Optional<PositionalAccuracy> getAccuracy(final IdentifiedObject object) { - final DatumEnsemble<?> ensemble; - if (object instanceof DatumEnsemble<?>) { - ensemble = (DatumEnsemble<?>) object; + final DefaultDatumEnsemble<?> ensemble; + if (object instanceof DefaultDatumEnsemble<?>) { + ensemble = (DefaultDatumEnsemble<?>) object; + } else if (object instanceof SingleCRS) { - ensemble = ((SingleCRS) object).getDatumEnsemble(); ++ ensemble = getDatumEnsemble((SingleCRS) object); + if (ensemble == null) { + return Optional.empty(); + } } else { return Optional.empty(); } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java index 2cb7303a74,82a0ae2c47..73bd9c4d48 --- 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 @@@ -93,12 -99,18 +96,11 @@@ public class DefaultVerticalDatum exten private static final long serialVersionUID = 380347456670516572L; /** - * The realization method (geoid, tidal, <i>etc.</i>), or {@code null} if unspecified. - * - * @see #getRealizationMethod() - */ - private RealizationMethod method; - - /** -- * The type of this vertical datum. - * If {@code null}, a value will be inferred from the name by {@link #type()}. ++ * The type of this vertical datum, or {@code null} if unspecified. ++ * In the latter case, a default will be inferred when requested. * - * @see #type() * @see #getVerticalDatumType() */ - @SuppressWarnings("deprecation") private VerticalDatumType type; /** @@@ -200,28 -231,18 +202,6 @@@ return VerticalDatum.class; } -- /** - * 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. - * - * <p>This method uses heuristic rules and may be changed in any future SIS version.</p> - * Returns the method through which this vertical reference frame is realized. -- * - * <p>No synchronization needed; this is not a problem if this value is computed twice. - * This method returns only existing immutable instances.</p> - * @return method through which this vertical reference frame is realized. -- * - * @see #getVerticalDatumType() - * @see #getTypeElement() - * @since 2.0 (temporary version number until this branch is released) -- */ - private VerticalDatumType type() { - VerticalDatumType t = type; - if (t == null) { - final ReferenceIdentifier name = super.getName(); - type = t = VerticalDatumTypes.fromDatum(name != null ? name.getCode() : null, super.getAlias(), null); - } - return t; - @Override - public Optional<RealizationMethod> getRealizationMethod() { - return Optional.ofNullable(method); -- } -- /** * Returns the type of this vertical datum. * @@@ -232,10 -253,33 +212,30 @@@ * 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(); ++ if (type == null) { ++ // Do not store the value because it depends on the context (whether we know the axis). ++ return VerticalDatumTypes.fromDatum(super.getName().getCode(), super.getAlias(), null); ++ } + 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 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); - })); ++ CoordinateSystemAxis axis = null; ++ if (parent instanceof CoordinateReferenceSystem) { ++ axis = ((CoordinateReferenceSystem) parent).getCoordinateSystem().getAxis(0); ++ } ++ return VerticalDatumTypes.fromDatum(getName().getCode(), getAlias(), axis); } /** @@@ -345,7 -402,7 +343,7 @@@ switch (mode) { case STRICT: { final var other = (DefaultVerticalDatum) object; - return type().equals(other.type()); - return Objects.equals(method, other.method) && Objects.equals(type, other.type); ++ return Objects.equals(type, other.type); } case BY_CONTRACT: { final var other = (VerticalDatum) object; @@@ -373,7 -431,7 +371,7 @@@ */ @Override protected long computeHashCode() { - return super.computeHashCode() + type().hashCode(); - return super.computeHashCode() + 37 * Objects.hashCode(method); ++ return super.computeHashCode() + Objects.hashCode(type); } /** @@@ -429,9 -487,13 +427,12 @@@ * * @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") 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 getOrGuessMethod(null); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java index 20f5dfb954,68cfaace7e..e53f7d944c --- 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 @@@ -180,12 -177,9 +180,15 @@@ abstract class AuthorityFactoryProxy<T */ static final AuthorityFactoryProxy<InternationalString> description(final Class<? extends IdentifiedObject> classe) { return new AuthorityFactoryProxy<InternationalString>(InternationalString.class, AuthorityFactoryIdentifier.Type.ANY) { - @Override InternationalString createFromAPI(AuthorityFactory factory, String code) throws FactoryException { + @Override InternationalString create(GeodeticAuthorityFactory factory, String code) throws FactoryException { return factory.getDescriptionText(classe, code).orElse(null); } + @Override InternationalString createFromAPI(AuthorityFactory factory, String code) throws FactoryException { ++ if (factory instanceof GeodeticAuthorityFactory) { ++ return ((GeodeticAuthorityFactory) factory).getDescriptionText(classe, code).orElse(null); ++ } + return factory.getDescriptionText(code); + } @Override AuthorityFactoryProxy<InternationalString> specialize(String typeName) { return this; } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java index dec2641dbf,c684889ab8..fb9e9c5164 --- 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 @@@ -178,24 -173,6 +178,24 @@@ public abstract class GeodeticAuthority return Optional.ofNullable(IdentifiedObjects.getDisplayName(createObject(type, code))); } + /** + * Returns a description of the object corresponding to a code. + * + * @param code value allocated by authority. + * @return a description of the object, or {@code null} if the object + * corresponding to the specified {@code code} has no description. + * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. + * @throws FactoryException if the query failed for some other reason. + * + * @deprecated This method is ambiguous because the EPSG geodetic registry may allocate + * the same code to different kinds of object. + */ + @Override + @Deprecated(since = "1.5") + public InternationalString getDescriptionText(final String code) throws FactoryException { - return getDescriptionText(IdentifiedObject.class, code).orElse(null); ++ return getDescriptionText(CoordinateReferenceSystem.class, code).orElse(null); + } + /** * Returns an object of the specified type from a code. This implementation forwards * the method call to the most specialized methods determined by the given type. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java index 2af9d2d64d,34d842d425..70c4797654 --- 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 @@@ -26,16 -27,26 +27,26 @@@ import org.opengis.util.FactoryExceptio 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; + ++// Specific to the main branch: ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; + /** * Searches in an authority factory for objects approximately equal to a given object. @@@ -255,13 -264,45 +264,45 @@@ public class IdentifiedObjectFinder } /** - * Returns {@code true} if a candidate found by {@code IdentifiedObjectFinder} should be considered equals to the - * requested object. This method invokes the {@code equals(…)} method on the {@code candidate} argument instead - * than on the user-specified {@code object} on the assumption that implementations coming from the factory are - * more reliable than user-specified objects. + * Returns the comparison mode to use when comparing a candidate against the object to search. + */ + private ComparisonMode getComparisonMode() { + return ignoreAxes ? ComparisonMode.ALLOW_VARIANT : ComparisonMode.APPROXIMATE; + } + + /** + * Returns {@code true} if a candidate created by a factory should be considered equal to the object to search. + * The {@code mode} and {@code proxy} arguments may be snapshots of the {@code IdentifiedObjectFinder}'s state + * taken at the time when the {@link Instances} iterable has been created. + * + * <h4>Implementation note</h4> + * This method invokes the {@code equals(…)} method on the {@code candidate} argument instead of {@code object} + * specified by the user on the assumption that implementations coming from the factory are more reliable than + * user-specified objects. + * + * @param candidate an object created by an authority factory. + * @param object the user-specified object to search. + * @param mode value of {@link #getComparisonMode()} (may be a snapshot). + * @param proxy value of {@link #proxy} (may be a snapshot). + * @return whether the given candidate can be considered equal to the object to search. + * + * @see #createAndFilter(AuthorityFactory, String, IdentifiedObject) */ - private boolean match(final IdentifiedObject candidate, final IdentifiedObject object) { - return Utilities.deepEquals(candidate, object, ignoreAxes ? ComparisonMode.ALLOW_VARIANT : COMPARISON_MODE); + private static boolean match(final IdentifiedObject candidate, final IdentifiedObject object, + final ComparisonMode mode, final AuthorityFactoryProxy<?> proxy) + { + if (Utilities.deepEquals(candidate, object, mode)) { + return true; + } + if (Datum.class.isAssignableFrom(proxy.type)) { - if (candidate instanceof Datum && object instanceof DatumEnsemble<?>) { - return DatumOrEnsemble.isLegacyDatum((DatumEnsemble<?>) object, (Datum) candidate, mode); ++ if (candidate instanceof Datum && object instanceof DefaultDatumEnsemble<?>) { ++ return DatumOrEnsemble.isLegacyDatum((DefaultDatumEnsemble<?>) object, (Datum) candidate, mode); + } - if (candidate instanceof DatumEnsemble<?> && object instanceof Datum) { - return DatumOrEnsemble.isLegacyDatum((DatumEnsemble<?>) candidate, (Datum) object, mode); ++ if (candidate instanceof DefaultDatumEnsemble<?> && object instanceof Datum) { ++ return DatumOrEnsemble.isLegacyDatum((DefaultDatumEnsemble<?>) candidate, (Datum) object, mode); + } + } + return false; } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java index b94cbce82e,e113272552..acacea9a0a --- 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,6 -64,6 +64,11 @@@ 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 branch: ++import org.opengis.referencing.ReferenceIdentifier; ++import org.apache.sis.referencing.crs.DefaultParametricCRS; ++import org.apache.sis.referencing.datum.DefaultParametricDatum; ++ // Specific to the main and geoapi-3.1 branches: import org.opengis.referencing.crs.GeneralDerivedCRS; @@@ -322,11 -364,15 +365,15 @@@ crs: if (isInstance(CoordinateRefere 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 DefaultParametricCRS) { ++ filter = dependencies("DATUM_CODE", DefaultParametricDatum.class, ((DefaultParametricCRS) object).getDatum(), 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 { @@@ -532,4 -583,214 +584,214 @@@ } buffer.append(')'); } + + /** + * Returns a set of authority codes that <strong>may</strong> identify the same object as the specified one. + * This implementation tries to get a smaller set than what {@link EPSGDataAccess#getAuthorityCodes(Class)} + * would produce. Deprecated objects must be last in iteration order. + * + * <h4>Exceptions during iteration</h4> + * An unchecked {@link BackingStoreException} may be thrown during the iteration + * if the action of fetching codes from database was delayed and that action failed. + * The exception cause may be {@link FactoryException} or {@link SQLException}. + * + * @param object the object to search in the database. + * @return codes of objects that may be the requested ones. + * @throws FactoryException if an error occurred while fetching the set of code candidates. + */ + @Override + protected Iterable<String> getCodeCandidates(final IdentifiedObject object) throws FactoryException { + for (final TableInfo source : TableInfo.values()) { + if (source.isSpecificEnough() && source.type.isInstance(object)) try { + return new CodeCandidates(object, source); + } catch (SQLException exception) { + throw databaseFailure(exception); + } + } + return Set.of(); + } + + /** + * Set of authority codes that <strong>may</strong> identify the same object as the specified one. + * This collection returns the codes that can be obtained easily before the more expensive searches. + * + * @todo We should not keep a reference to the enclosing finder, because the {@link EPSGDataAccess} + * may become invalid before the iteration is completed. For now, this is not a problem because this + * collection is copied by the {@link EPSGFactory} finder. But this is suboptimal because it defeats + * the purpose of object lazy instantiation. + */ + private final class CodeCandidates implements Iterable<String>, Disposable { + /** The object to search. */ + private final IdentifiedObject object; + + /** Workaround for a Derby bug (see {@code filterFalsePositive(…)}). */ + private String name; + + /** {@code LIKE} Pattern of the name of the object to search. */ + private String namePattern; + + /** Information about the tables of the object to search. */ + private final TableInfo source; + + /** Cache of codes found so far. */ + private final Set<Integer> codes; + + /** Snapshot of the search domain as it was at collection construction time. */ + private final Domain domain; + + /** Sequential number of the algorithm used for filling the {@link #codes} collection so far. */ + private byte searchMethod; + + /** + * Creates a lazy collection of code candidates. + * This constructor loads immediately some codes in order to have an exception early in case of problem. + * + * @param object the object to search in the database. + * @param source information about the table where to search for the object. + * @throws SQLException if an error occurred while searching for codes associated to names. + * @throws FactoryException if an error occurred while fetching the set of code candidates. + */ + CodeCandidates(final IdentifiedObject object, final TableInfo source) throws SQLException, FactoryException { + this.object = object; + this.source = source; + this.domain = getSearchDomain(); + this.codes = new LinkedHashSet<>(); + if (domain != Domain.EXHAUSTIVE_VALID_DATASET) { - for (final Identifier id : object.getIdentifiers()) { ++ for (final ReferenceIdentifier id : object.getIdentifiers()) { + if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) try { + codes.add(Integer.valueOf(id.getCode())); + } catch (NumberFormatException exception) { + Logging.ignorableException(EPSGDataAccess.LOGGER, IdentifiedObjectFinder.class, "find", exception); + } + } + } + if (codes.isEmpty()) { + fetchMoreCodes(codes); + } + } + + /** + * 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() { + searchMethod = 3; // The value after the last switch in `fetchMoreCodes(Collection)`. + } + + /** + * Populates the given collection with code candidates. + * This method tries less expansive search methods before to tries more expensive search methods. + * + * @param addTo an initially empty collection where to add the codes. + * @return whether at least one code has been added to the given collection. + * @throws SQLException if an error occurred while searching for codes associated to names. + * @throws FactoryException if an error occurred while fetching the set of code candidates. + */ + private boolean fetchMoreCodes(final Collection<Integer> addTo) throws SQLException, FactoryException { + do { + switch (searchMethod) { + case 0: { // Fetch codes from the name. + if (domain != Domain.EXHAUSTIVE_VALID_DATASET) { + name = getName(object); + if (name != null) { // Should never be null, but we are paranoiac. + namePattern = dao.toLikePattern(name); + dao.findCodesFromName(source, TableInfo.toCacheKey(object), namePattern, name, addTo); + } + } + break; + } + case 1: { // Fetch codes from the aliases. + if (domain != Domain.EXHAUSTIVE_VALID_DATASET) { + if (namePattern != null) { + dao.findCodesFromAlias(source, namePattern, name, addTo); + } + } + break; + } + case 2: { // Search codes based on object properties. + if (domain != Domain.DECLARATION) { + searchCodesFromProperties(object, domain == Domain.ALL_DATASET, addTo); + } + break; + } + default: { + return false; + } + } + searchMethod++; + } while (addTo.isEmpty()); + return true; + } + + /** + * Returns additional code candidates which were not yet returned by the iteration. + * This method uses the next search method which hasn't be tried. + * + * @return the additional codes. + * @throws BackingStoreException if an error occurred while fetching the set of code candidates. + */ + private Iterator<Integer> fetchMoreCodes() { + final var addTo = new ArrayList<Integer>(); + do { + try { + if (!fetchMoreCodes(addTo)) break; + } catch (SQLException | FactoryException exception) { + throw new BackingStoreException(exception); + } + for (Iterator<Integer> it = addTo.iterator(); it.hasNext();) { + if (!codes.add(it.next())) { + it.remove(); // Code has already be returned. + } + } + } while (addTo.isEmpty()); + return addTo.iterator(); + } + + /** + * Returns an iterator over the code candidates. The codes are cached: + * the should not be fetched again if a second iteration is executed. + * + * <h4>Limitation</h4> + * The current implementation does not support concurrent iterations, even in the same thread. + * This is okay for the usage that Apache <abbr>SIS</abbr> is making of this iterator. + */ + @Override + public Iterator<String> iterator() { + return new Iterator<String>() { + /** Iterator over a subset of the codes. */ + private Iterator<Integer> sources = codes.iterator(); + + /** Tests whether there is more codes to return. */ + @Override public boolean hasNext() { + if (sources.hasNext()) { + return true; + } + sources = fetchMoreCodes(); + return sources.hasNext(); + } + + /** Returns the next code. */ + @Override public String next() { + if (!sources.hasNext()) { + sources = fetchMoreCodes(); + } + return sources.next().toString(); + } + }; + } + + /** + * Returns a string representation for debugging purposes. + * The {@code "size"} property may change during the iteration. + */ + @Override + public String toString() { + return Strings.toString(getClass(), "object", getName(object), "source", source, "domain", domain, "size", codes.size()); + } + } } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java index 8d624ffa7f,edf2c685d9..ec3bc59639 --- 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 @@@ -1998,11 -1981,11 +2006,11 @@@ search: try (ResultSet result = execute final IdentifiedObject conventionalRS = createConventionalRS(convRSCode); @SuppressWarnings("LocalVariableHidesMemberVariable") final Map<String,Object> properties = createProperties( - "Datum", epsg, name, null, area, scope, remarks, deprecated); + TableInfo.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); @@@ -3006,8 -2977,8 +3004,8 @@@ next: while (r.next() */ @SuppressWarnings("LocalVariableHidesMemberVariable") final Map<String,Object> properties = createProperties( - "Coordinate_Operation Parameter", epsg, name, null, null, null, isReversible, deprecated); + TableInfo.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. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java index 39925454cf,835c89fb5e..20bc526972 --- 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,139 -26,193 +26,200 @@@ 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 main branch: ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; ++import org.apache.sis.referencing.datum.DefaultGeodeticDatum; +import org.apache.sis.referencing.crs.DefaultParametricCRS; +import org.apache.sis.referencing.cs.DefaultParametricCS; +import org.apache.sis.referencing.datum.DefaultParametricDatum; + /** - * 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 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, GeocentricCRS.class, + VerticalCRS.class, CompoundCRS.class, EngineeringCRS.class, - DerivedCRS.class, TemporalCRS.class, ParametricCRS.class}, // See comment below ++ DerivedCRS.class, TemporalCRS.class, DefaultParametricCRS.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. ++ new Class<?>[] { DefaultDatumEnsemble.class, // Need to be first because Apache SIS uses as mixin interface. + GeodeticDatum.class, VerticalDatum.class, EngineeringDatum.class, - TemporalDatum.class, ParametricDatum.class}, ++ TemporalDatum.class, DefaultParametricDatum.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}, ++ TimeCS.class, DefaultParametricCS.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. */ - 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, DefaultParametricCRS.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, DefaultParametricCS.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, DefaultParametricDatum.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). @@@ -309,16 -404,46 +411,46 @@@ buffer.append(" WHERE "); if (typeColumn != null) { for (int i=0; i<subTypes.length; i++) { - final Class<?> candidate = subTypes[i]; - if (candidate.isAssignableFrom(userType)) { - if (factory.translator.useEnumerations()) { - buffer.append("CAST(").append(typeColumn).append(" AS ") - .append(EPSGInstaller.ENUM_REPLACEMENT).append(')'); - } else { - buffer.append(typeColumn); + final Class<?> subType = subTypes[i]; + if (subType.isAssignableFrom(userType)) { + /* + * Found the type to request in the `COORD_REF_SYS_KIND` or `DATUM_TYPE` columns. + * The mixin interfaces need to be handled in a special way. + */ + String typeName = typeNames[i]; - if (DynamicReferenceFrame.class.isAssignableFrom(userType)) { ++ if (DefaultGeodeticDatum.Dynamic.class.isAssignableFrom(userType)) { + typeName = DYNAMIC_TYPES.getOrDefault(typeName, typeName); + } + /* + * We may need to look for more than one type if some information are missing + * (for example, the dimension when EPSG distinguishes the 2D and 3D cases). + */ + String[] synonymous = SYNONYMOUS_TYPES.get(typeName); + if (synonymous != null && dimension > 0 && dimension <= 9) { + final String suffix = "2D".replace('2', (char) ('0' + dimension)); + if (typeName.endsWith(suffix)) { + synonymous = null; + } else { + for (String alternative : synonymous) { + if (alternative.endsWith(suffix)) { + typeName = alternative; + synonymous = null; + break; + } + } + } } - buffer.append(" LIKE '").append(typeNames[i]).append("%' AND "); - return candidate; + /* + * Build the SQL `WHERE` clause. + */ + buffer.append('(').append(typeColumn).append(" = '").append(typeName).append('\''); + if (synonymous != null) { + for (String alternative : synonymous) { + buffer.append(" OR ").append(typeColumn).append(" = '").append(alternative).append('\''); + } + } + buffer.append(") AND "); + return subType; } } } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java index 914cb6be33,40f545525a..7e6de959a8 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionTransformer.java @@@ -28,10 -29,10 +29,9 @@@ import org.apache.sis.referencing.CRS import org.apache.sis.referencing.MultiRegisterOperations; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.geometry.GeneralDirectPosition; - import org.apache.sis.util.Utilities; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.RegisterOperations; -import org.opengis.coordinate.MismatchedDimensionException; +// Specific to the main branch: +import org.opengis.geometry.MismatchedDimensionException; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java index bb0c5c49d3,e2aacdd5c1..22994f3543 --- 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 @@@ -207,9 -265,10 +210,10 @@@ public final class VerticalDatumTypes i AxisDirection dir = AxisDirection.UP; // Expected direction for accepting the type. switch (abbreviation.charAt(0)) { case 'h': method = ellipsoidal(); break; - case 'H': method = RealizationMethod.GEOID; break; + case 'H': method = VerticalDatumType.GEOIDAL; break; + case 'd': // Fall through - case 'D': method = RealizationMethod.TIDAL; dir = AxisDirection.DOWN; break; + case 'D': method = VerticalDatumType.DEPTH; dir = AxisDirection.DOWN; break; - default: return VerticalDatumType.OTHER_SURFACE; + default: return null; } if (dir.equals(axis.getDirection())) { return method; @@@ -229,24 -288,18 +233,29 @@@ * @param name name of the datum for which to guess a realization method, or {@code null}. * @return a realization method, or {@code null} if none can be guessed. */ - private static RealizationMethod 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.regionMatches(true, 0, "geoid", 0, 5)) { - return VerticalDatumType.GEOIDAL; - } + int i = 0; + do { + if (name.regionMatches(true, i, "geoid", 0, 5)) { - return RealizationMethod.GEOID; ++ return VerticalDatumType.GEOIDAL; + } + i = name.indexOf(' ', i) + 1; + } while (i > 0); + if (name.equalsIgnoreCase("Tidal")) { + return VerticalDatumType.DEPTH; + } - for (int i=0; i<name.length();) { ++ i = 0; ++ while (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/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java index 12fef44803,7b6a453186..8ad69565f3 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java @@@ -920,10 -856,10 +918,10 @@@ check: for (int isTarget=0; ; isTa * - Scope, domain and accuracy properties only if NOT in "ignore metadata" mode. * - Interpolation CRS in all cases (regardless if ignoring metadata or not). */ - final CoordinateOperation that = (CoordinateOperation) object; + final var that = (CoordinateOperation) object; if ((mode.isIgnoringMetadata() || (deepEquals(getCoordinateOperationAccuracy(), that.getCoordinateOperationAccuracy(), mode))) && - deepEquals(getInterpolationCRS(), that.getInterpolationCRS(), mode)) + deepEquals(getInterpolationCRS().orElse(null), getInterpolationCRS(that), mode)) { /* * At this point all metadata match or can be ignored. First, compare the targetCRS. diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java index d7a85f04cd,eade998322..eeb4852a02 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java @@@ -108,10 -119,10 +122,10 @@@ public final class Assertions extends S */ public static void assertEpsgIdentifierEquals(final String expected, final Identifier actual) { assertNotNull(actual); - assertEquals(expected, actual.getCode(), "code"); + assertLegacyEquals(expected, actual.getCode(), "code"); - assertEquals(Constants.EPSG, actual.getCodeSpace(), "codeSpace"); + assertEquals(Constants.EPSG, (actual instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) actual).getCodeSpace() : null, "codeSpace"); assertEquals(Constants.EPSG, Citations.toCodeSpace(actual.getAuthority()), "authority"); - assertEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected, IdentifiedObjects.toString(actual), "identifier"); + assertLegacyEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected, IdentifiedObjects.toString(actual), "identifier"); } /** diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java index a9e651991e,671fb48832..28584bcf7a --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java @@@ -76,9 -72,9 +75,9 @@@ public final class IdentifiedObjectFind * Same test as above, using a CRS without identifier. * The intent is to force a full scan. */ - final CoordinateReferenceSystem search = new DefaultGeographicCRS( + final var search = new DefaultGeographicCRS( Map.of(DefaultGeographicCRS.NAME_KEY, CRS84.getName()), - CRS84.getDatum(), CRS84.getDatumEnsemble(), CRS84.getCoordinateSystem()); + CRS84.getDatum(), getDatumEnsemble(CRS84), CRS84.getCoordinateSystem()); assertEqualsIgnoreMetadata(CRS84, search); // Required condition for next test. finder.setSearchDomain(IdentifiedObjectFinder.Domain.DECLARATION); diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java index 99865879a7,99865879a7..87bd14d01f --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java @@@ -196,7 -196,7 +196,7 @@@ public final class MultiAuthoritiesFact assertSame(HardCodedDatum.SPHERE, factory.createGeodeticDatum("MOCK: 0:6047")); assertSame(Extents .WORLD, factory.createExtent ("MOCK: 2.3 : 1262")); assertSame(Units .METRE, factory.createUnit (" MoCK : : 9001 ")); -- assertEquals("Greenwich", factory.getDescriptionText(IdentifiedObject.class, "MOCK:8901").get().toString()); ++ assertEquals("Greenwich", factory.getDescriptionText(PrimeMeridian.class, "MOCK:8901").get().toString()); var e = assertThrows(NoSuchAuthorityFactoryException.class, () -> factory.createGeodeticDatum("MOCK2:4326"), diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java index 714b681bb9,33fdd7fa1a..3850bc2ef1 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java @@@ -101,6 -116,26 +116,26 @@@ public final class ConsistencyTest exte lookup(parseAndFormat(format, code, crs), crs); } + /** + * Returns whether testing the given <abbr>CRS</abbr> requires the 2019 version of <abbr>WKT</abbr> format. + * We skip the vertical datum having the "local" realization method because this information is lost during + * the roundtrip with WKT or WKT 2 version 2015, and the WKT parser tries to guess the method from the axis + * abbreviation "d" which result in "tidal". + */ + private static boolean requiresWKT2019(final CoordinateReferenceSystem crs) { + final VerticalCRS c = CRS.getVerticalComponent(crs, false); + if (c != null) { + final var datum = c.getDatum(); + if (datum != null) { - final String method = datum.getRealizationMethod().map(CodeList::name).orElse(""); ++ final String method = datum.getVerticalDatumType().name(); + if (method.equalsIgnoreCase("local")) { + return true; + } + } + } + return false; + } + /** * Verifies the WKT consistency of all CRS instances. * diff --cc endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java index ff31e233f0,79cd823110..cfa89aaad2 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java @@@ -34,15 -34,16 +34,15 @@@ import org.apache.sis.geometry.wrapper. import org.apache.sis.geometry.wrapper.GeometryWrapper; import org.apache.sis.metadata.sql.privy.SQLBuilder; import org.apache.sis.filter.privy.WarningEvent; + import org.apache.sis.referencing.CRS; import org.apache.sis.storage.FeatureSet; import org.apache.sis.system.Modules; - import org.apache.sis.util.Utilities; import org.apache.sis.util.Workaround; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.util.CodeList; -import org.opengis.feature.Feature; -import org.opengis.filter.Filter; -import org.opengis.filter.ValueReference; +// Specific to the main branch: +import org.apache.sis.filter.Filter; +import org.apache.sis.feature.AbstractFeature; +import org.apache.sis.pending.geoapi.filter.ValueReference; /** diff --cc optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java index 60292eef43,4c8393cdcc..f024ec9597 --- a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java +++ b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/EmbeddedResourcesTest.java @@@ -133,8 -129,8 +129,8 @@@ public final strictfp class EmbeddedRes */ @Test public void testCrsforCode() throws FactoryException { - assumeDataPresent(); + assumeContainsEPSG(); - CoordinateReferenceSystem crs = CRS.forCode("EPSG:6676"); + var crs = assertInstanceOf(AbstractCRS.class, CRS.forCode("EPSG:6676")); String area = TestUtilities.getSingleton(crs.getDomains()).getDomainOfValidity().getDescription().toString(); assertTrue(area.contains("Japan"), area); }
