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 92cb82585e83a19671af5cd91b810669b8b33e58 Merge: 23f2e78a1a 11f6114609 Author: Martin Desruisseaux <[email protected]> AuthorDate: Mon Sep 8 19:18:23 2025 +0200 Merge branch 'geoapi-3.1' .../main/org/apache/sis/console/CommandRunner.java | 2 +- .../apache/sis/console/FormattedOutputCommand.java | 6 + .../org/apache/sis/console/CRSCommandTest.java | 15 +- .../sis/metadata/iso/citation/Citations.java | 2 +- .../org/apache/sis/temporal/LenientDateFormat.java | 4 +- .../sis/metadata/iso/citation/CitationsTest.java | 2 +- .../sis/coordinate/DefaultCoordinateMetadata.java | 2 +- .../main/org/apache/sis/io/wkt/AbstractParser.java | 82 +++ .../main/org/apache/sis/io/wkt/Convention.java | 122 ++--- .../main/org/apache/sis/io/wkt/Element.java | 18 +- .../org/apache/sis/io/wkt/FormattableObject.java | 10 +- .../main/org/apache/sis/io/wkt/Formatter.java | 102 ++-- .../apache/sis/io/wkt/GeodeticObjectParser.java | 559 ++++++++++++++------- .../org/apache/sis/io/wkt/MathTransformParser.java | 27 +- .../main/org/apache/sis/io/wkt/Transliterator.java | 21 +- .../main/org/apache/sis/io/wkt/WKTFormat.java | 12 +- .../main/org/apache/sis/io/wkt/package-info.java | 11 +- .../sis/parameter/DefaultParameterValue.java | 167 +++--- .../org/apache/sis/parameter/TensorParameters.java | 4 +- .../pending/geoapi/referencing/MissingMethods.java | 167 +++++- .../sis/referencing/AbstractIdentifiedObject.java | 20 +- .../main/org/apache/sis/referencing/CRS.java | 12 +- .../sis/referencing/DefaultObjectDomain.java | 1 + .../sis/referencing/ImmutableIdentifier.java | 2 - .../apache/sis/referencing/crs/AbstractCRS.java | 88 ++-- .../sis/referencing/crs/AbstractDerivedCRS.java | 14 + .../sis/referencing/crs/DefaultCompoundCRS.java | 14 +- .../sis/referencing/crs/DefaultDerivedCRS.java | 44 +- .../sis/referencing/crs/DefaultEngineeringCRS.java | 20 +- .../sis/referencing/crs/DefaultGeocentricCRS.java | 2 - .../sis/referencing/crs/DefaultGeodeticCRS.java | 43 +- .../sis/referencing/crs/DefaultGeographicCRS.java | 6 +- .../sis/referencing/crs/DefaultImageCRS.java | 2 - .../sis/referencing/crs/DefaultParametricCRS.java | 12 - .../sis/referencing/crs/DefaultProjectedCRS.java | 14 +- .../sis/referencing/crs/DefaultTemporalCRS.java | 20 +- .../sis/referencing/crs/DefaultVerticalCRS.java | 20 +- .../org/apache/sis/referencing/crs/DynamicCRS.java | 77 +++ .../org/apache/sis/referencing/cs/AbstractCS.java | 2 - .../sis/referencing/cs/CoordinateSystems.java | 3 +- .../cs/DefaultCoordinateSystemAxis.java | 109 ++-- .../sis/referencing/cs/DirectionAlongMeridian.java | 2 - .../sis/referencing/datum/AbstractDatum.java | 32 +- .../referencing/datum/DefaultDatumEnsemble.java | 76 ++- .../sis/referencing/datum/DefaultEllipsoid.java | 2 - .../referencing/datum/DefaultEngineeringDatum.java | 16 +- .../referencing/datum/DefaultGeodeticDatum.java | 12 +- .../sis/referencing/datum/DefaultImageDatum.java | 23 +- .../referencing/datum/DefaultParametricDatum.java | 18 +- .../referencing/datum/DefaultPrimeMeridian.java | 4 +- .../referencing/datum/DefaultTemporalDatum.java | 16 +- .../referencing/datum/DefaultVerticalDatum.java | 11 +- .../referencing/factory/GeodeticObjectFactory.java | 6 +- .../referencing/factory/sql/EPSGDataAccess.java | 13 +- .../org/apache/sis/referencing/internal/Epoch.java | 69 ++- .../internal/PositionalAccuracyConstant.java | 58 ++- .../operation/AbstractCoordinateOperation.java | 12 +- .../operation/DefaultOperationMethod.java | 2 - .../provider/MercatorAuxiliarySphere.java | 3 +- .../referencing/operation/provider/Mollweide.java | 3 +- .../provider/ObliqueMercatorTwoPoints.java | 5 +- .../transform/DefaultMathTransformFactory.java | 3 +- .../apache/sis/referencing/privy/WKTKeywords.java | 54 +- .../apache/sis/referencing/privy/WKTUtilities.java | 87 +--- .../org/apache/sis/geometry/ArrayEnvelopeTest.java | 11 +- .../apache/sis/geometry/GeneralEnvelopeTest.java | 3 +- .../test/org/apache/sis/io/wkt/ElementTest.java | 4 +- .../sis/io/wkt/GeodeticObjectParserTest.java | 143 +++++- .../test/org/apache/sis/io/wkt/WKTFormatTest.java | 1 + .../DefaultParameterDescriptorGroupTest.java | 7 +- .../parameter/DefaultParameterDescriptorTest.java | 6 +- .../sis/parameter/DefaultParameterValueTest.java | 3 +- .../org/apache/sis/parameter/TensorValuesTest.java | 5 +- .../referencing/AbstractReferenceSystemTest.java | 36 +- .../org/apache/sis/referencing/Assertions.java | 18 +- .../referencing/crs/DefaultCompoundCRSTest.java | 9 +- .../sis/referencing/crs/DefaultDerivedCRSTest.java | 4 +- .../referencing/crs/DefaultEngineeringCRSTest.java | 2 +- .../referencing/crs/DefaultGeocentricCRSTest.java | 5 +- .../referencing/crs/DefaultGeographicCRSTest.java | 27 +- .../sis/referencing/crs/DefaultImageCRSTest.java | 2 +- .../referencing/crs/DefaultProjectedCRSTest.java | 25 +- .../referencing/crs/DefaultTemporalCRSTest.java | 2 +- .../referencing/crs/DefaultVerticalCRSTest.java | 2 +- .../cs/DefaultCoordinateSystemAxisTest.java | 52 +- .../referencing/datum/DefaultEllipsoidTest.java | 5 +- .../datum/DefaultGeodeticDatumTest.java | 12 +- .../datum/DefaultVerticalDatumTest.java | 4 +- .../factory/CommonAuthorityFactoryTest.java | 2 +- .../apache/sis/referencing/internal/EpochTest.java | 22 +- .../operation/CoordinateOperationFinderTest.java | 6 +- .../operation/CoordinateOperationRegistryTest.java | 10 +- .../DefaultConcatenatedOperationTest.java | 4 +- .../DefaultCoordinateOperationFactoryTest.java | 6 +- .../operation/DefaultOperationMethodTest.java | 2 +- .../operation/DefaultTransformationTest.java | 2 +- .../operation/HardCodedConversions.java | 5 +- .../referencing/operation/provider/AffineTest.java | 7 +- .../operation/provider/LongitudeRotationTest.java | 3 +- .../report/CoordinateOperationMethods.java | 8 +- .../sis/test/integration/ConsistencyTest.java | 7 +- .../org/apache/sis/storage/base/PRJDataStore.java | 14 +- .../apache/sis/storage/esri/WritableStoreTest.java | 2 +- .../main/org/apache/sis/setup/GeometryLibrary.java | 3 +- .../main/org/apache/sis/util/Characters.java | 1 - .../main/org/apache/sis/util/privy/Constants.java | 5 + .../main/org/apache/sis/util/privy/URLs.java | 5 - .../org/apache/sis/gui/referencing/WKTPane.java | 2 + 108 files changed, 1817 insertions(+), 1074 deletions(-) diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java index a6dda98fbb,55c5408246..5928ff070e --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java @@@ -88,12 -91,9 +90,11 @@@ import org.apache.sis.geometry.Abstract import org.apache.sis.geometry.AbstractEnvelope; import org.apache.sis.xml.NilObject; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.util.ControlledVocabulary; -import org.opengis.referencing.ObjectDomain; +// Specific to the main branch: +import org.opengis.util.CodeList; +import org.opengis.referencing.ReferenceIdentifier; +import org.apache.sis.referencing.DefaultObjectDomain; - import org.apache.sis.referencing.internal.Legacy; +import org.apache.sis.referencing.datum.AbstractDatum; /** @@@ -868,11 -883,8 +884,8 @@@ public class Formatter implements Local } } } - for (Identifier id : identifiers) { + for (ReferenceIdentifier id : identifiers) { - if (!(id instanceof FormattableObject)) { - id = ImmutableIdentifier.castOrCopy(id); - } - append((FormattableObject) id); + appendFormattable(id, ImmutableIdentifier::castOrCopy); if (filterID) break; } } @@@ -889,21 -901,29 +902,32 @@@ * {@link ReferenceSystem} and {@link CoordinateOperation} objects. */ private void appendForSubtypes(final IdentifiedObject object) { - InternationalString anchor = null, scope = null; - Extent area = null; - if (object instanceof Datum) { - final var datum = (Datum) object; + if (object instanceof AbstractDatum) { - anchor = ((AbstractDatum) object).getAnchorPoint(); ++ final var datum = (AbstractDatum) object; + appendOnNewLine(WKTKeywords.Anchor, datum.getAnchorDefinition().orElse(null), null); + datum.getAnchorEpoch().ifPresent((epoch) -> append(new Epoch(epoch, WKTKeywords.AnchorEpoch))); } else if (!(object instanceof ReferenceSystem || object instanceof CoordinateOperation)) { return; } - for (final DefaultObjectDomain domain : Legacy.getDomains(object)) { - scope = domain.getScope(); - area = domain.getDomainOfValidity(); - if (area != null) break; - // TODO: in 2019 revision we need to format all USAGE[…] elements, not only the first one. ++ if (!(object instanceof AbstractIdentifiedObject)) { ++ return; ++ } + final boolean usage = convention.supports(Convention.WKT2_2019); - for (final ObjectDomain domain : object.getDomains()) { ++ for (final DefaultObjectDomain domain : ((AbstractIdentifiedObject) object).getDomains()) { + if (usage) { + // ISO 19162:2019 + newLine(); - appendFormattable(domain, DefaultObjectDomain::castOrCopy); ++ append(domain); + } else { + // ISO 19162:2015 + InternationalString scope = domain.getScope(); + Extent area = domain.getDomainOfValidity(); + if (scope != null || area != null) { + append(scope, area); + break; + } + } } - appendOnNewLine(WKTKeywords.Anchor, anchor, null); - append(scope, area); } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java index 1291af32e8,94eb593acc..186903334b --- 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 @@@ -87,13 -91,8 +91,16 @@@ import org.apache.sis.util.iso.Types // Specific to the main and geoapi-3.1 branches: import org.opengis.referencing.ReferenceIdentifier; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.ObjectDomain; +// Specific to the main branch: +import org.opengis.referencing.ReferenceSystem; +import org.apache.sis.pending.geoapi.referencing.MissingMethods; +import org.apache.sis.referencing.cs.DefaultParametricCS; ++import org.apache.sis.referencing.datum.AbstractDatum; ++import org.apache.sis.referencing.datum.DefaultDatumEnsemble; ++import org.apache.sis.referencing.datum.DefaultVerticalDatum; +import org.apache.sis.referencing.datum.DefaultParametricDatum; +import org.apache.sis.referencing.factory.GeodeticObjectFactory; +import org.apache.sis.temporal.TemporalDate; /** @@@ -476,94 -462,45 +478,45 @@@ class GeodeticObjectParser extends Math // REMINDER: values associated to IDENTIFIERS_KEY shall be recognized by `toIdentifier(Object)`. } /* - * Other metadata (SCOPE, AREA, etc.). ISO 19162 said that at most one of each type shall be present, - * but our parser accepts an arbitrary number of some kinds of metadata. They can be recognized by the - * `while` loop. + * SCOPE, AREA, BBOX, VERTICALEXTENT and TIMEEXTENT. Since ISO 19162:2019, + * all those properties are grouped inside an USAGE element. Example: * - * Most WKT do not contain any of those metadata, so we perform an `isEmpty()` check as an optimization - * for those common cases. + * USAGE[ + * SCOPE["Large scale mapping."], + * AREA["UK - Northern Ireland - onshore"]] */ - if (!parent.isEmpty()) { - /* - * Example: SCOPE["Large scale topographic mapping and cadastre."] - */ - element = parent.pullElement(OPTIONAL, WKTKeywords.Scope); - if (element != null) { - properties.put(ReferenceSystem.SCOPE_KEY, element.pullString("scope")); // Other types like Datum use the same key. - element.close(ignoredElements); - } - /* - * Example: AREA["Netherlands offshore."] - */ - DefaultExtent extent = null; - while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Area)) != null) { - final String area = element.pullString("area"); - element.close(ignoredElements); - if (extent == null) { - extent = new DefaultExtent(area, null, null, null); - } else { - extent.getGeographicElements().add(new DefaultGeographicDescription(area)); - } - } - /* - * Example: BBOX[51.43, 2.54, 55.77, 6.40] - */ - while ((element = parent.pullElement(OPTIONAL, WKTKeywords.BBox)) != null) { - final double southBoundLatitude = element.pullDouble("southBoundLatitude"); - final double westBoundLongitude = element.pullDouble("westBoundLongitude"); - final double northBoundLatitude = element.pullDouble("northBoundLatitude"); - final double eastBoundLongitude = element.pullDouble("eastBoundLongitude"); - element.close(ignoredElements); - if (extent == null) extent = new DefaultExtent(); - extent.getGeographicElements().add(new DefaultGeographicBoundingBox( - westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude)); - } - /* - * Example: VERTICALEXTENT[-1000, 0, LENGTHUNIT[“metre”, 1]] - * - * Units are optional, default to metres (no "contextual units" here). - */ - while ((element = parent.pullElement(OPTIONAL, WKTKeywords.VerticalExtent)) != null) { - final double minimum = element.pullDouble("minimum"); - final double maximum = element.pullDouble("maximum"); - Unit<Length> unit = parseScaledUnit(element, WKTKeywords.LengthUnit, Units.METRE); - element.close(ignoredElements); - if (unit == null) unit = Units.METRE; - if (extent == null) extent = new DefaultExtent(); - verticalElements = new VerticalInfo(verticalElements, extent, minimum, maximum, unit).resolve(verticalCRS); - final var domains = new ArrayList<ObjectDomain>(); ++ final var domains = new ArrayList<DefaultObjectDomain>(); + while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Usage)) != null) { + final String scope = pullElementAsString(element, WKTKeywords.Scope); + final DefaultExtent extent = parseExtent(element); + if (scope != null || extent != null) { + domains.add(new DefaultObjectDomain(Types.toInternationalString(scope), extent)); } + element.close(ignoredElements); + } + if (domains.isEmpty()) { /* - * Example: TIMEEXTENT[2013-01-01, 2013-12-31] - * - * TODO: syntax like TIMEEXTENT[“Jurassic”, “Quaternary”] is not yet supported. - * See https://issues.apache.org/jira/browse/SIS-163 + * Legacy (ISO 19162:2015) way to declare scope and extent: + * directly inside de CRS element, without USAGE wrapper. */ - while ((element = parent.pullElement(OPTIONAL, WKTKeywords.TimeExtent)) != null) { - if (element.peekValue() instanceof String) { - element.pullString("startTime"); - element.pullString("endTime"); - element.close(ignoredElements); - warning(parent, element, Errors.formatInternational(Errors.Keys.UnsupportedType_1, "TimeExtent[String,String]"), null); - } else { - final Instant startTime = element.pullDate("startTime"); - final Instant endTime = element.pullDate("endTime"); - element.close(ignoredElements); - final var t = new DefaultTemporalExtent(startTime, endTime); - if (extent == null) extent = new DefaultExtent(); - extent.getTemporalElements().add(t); - } + final String scope = pullElementAsString(parent, WKTKeywords.Scope); + if (scope != null) { - properties.put(ObjectDomain.SCOPE_KEY, scope); ++ properties.put(ReferenceSystem.SCOPE_KEY, scope); } + final DefaultExtent extent = parseExtent(parent); if (extent != null) { - properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, extent); + properties.put(ReferenceSystem.DOMAIN_OF_VALIDITY_KEY, extent); } - /* - * Example: REMARK["Замечание на русском языке"] - */ - element = parent.pullElement(OPTIONAL, WKTKeywords.Remark); - if (element != null) { - properties.put(IdentifiedObject.REMARKS_KEY, element.pullString("remarks")); - element.close(ignoredElements); - } + } else { - properties.put(IdentifiedObject.DOMAINS_KEY, domains.toArray(ObjectDomain[]::new)); ++ properties.put(AbstractDatum.DOMAINS_KEY, domains.toArray(DefaultObjectDomain[]::new)); + } + /* + * Example: REMARK["Замечание на русском языке"] + */ + element = parent.pullElement(OPTIONAL, WKTKeywords.Remark); + if (element != null) { + properties.put(IdentifiedObject.REMARKS_KEY, element.pullString("remarks")); + element.close(ignoredElements); } parent.close(ignoredElements); if (parent.isRoot) { @@@ -577,15 -514,94 +530,94 @@@ * String, IdentifiedObject)} method. If an anchor has been found, its value is stored in the returned map. */ private Map<String,Object> parseAnchorAndClose(final Element element, final String name) throws ParseException { - final Element anchor = element.pullElement(OPTIONAL, WKTKeywords.Anchor); + String anchor = pullElementAsString(element, WKTKeywords.Anchor); + Temporal epoch = Epoch.fromYear(pullElementAsDouble(element, WKTKeywords.AnchorEpoch, OPTIONAL), 0); final Map<String,Object> properties = parseMetadataAndClose(element, name, null); - if (anchor != null) { - properties.put(Datum.ANCHOR_POINT_KEY, anchor.pullString(DefaultGeodeticDatum.ANCHOR_DEFINITION_KEY)); - anchor.close(ignoredElements); - } - if (anchor != null) properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor); - if (epoch != null) properties.put(Datum.ANCHOR_EPOCH_KEY, epoch); ++ if (anchor != null) properties.put(AbstractDatum.ANCHOR_DEFINITION_KEY, anchor); ++ if (epoch != null) properties.put(AbstractDatum.ANCHOR_EPOCH_KEY, epoch); return properties; } + /** + * Parses the {@code AREA}, {@code BBOX}, {@code VERTICALEXTENT} and {@code TIMEEXTENT} elements if present. + * These elements were directly inside the <abbr>CRS</abbr> element in <abbr>ISO</abbr> 19162:2015, but became + * wrapped inside an {@code USAGE} element in <abbr>ISO</abbr> 19162:2019. + * + * <h4>Extension to <abbr>ISO</abbr> 19162 specification</h4> - * The specification saids that at most one extent of each type can appear in the same {@code USAGE}. ++ * The specification said that at most one extent of each type can appear in the same {@code USAGE}. + * However, Apache <abbr>SIS</abbr> puts no limit on the number of occurrence of each extent type. + * + * <h4>Limitations</h4> + * Temporal extents with a syntax such as {@code TIMEEXTENT[“Jurassic”, “Quaternary”]} are not yet supported. + * See <a href="https://issues.apache.org/jira/browse/SIS-163">SIS-163</a>. + * + * @param parent the parent element. + * @return an extent containing all the above-cited elements, or {@code null} if none. + * @throws ParseException if an element cannot be parsed. + */ + private DefaultExtent parseExtent(final Element parent) throws ParseException { + DefaultExtent extent = null; + Element element; + /* + * Example: AREA["Netherlands offshore."] + */ + while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Area)) != null) { + final String area = element.pullString("area"); + element.close(ignoredElements); + if (extent == null) { + extent = new DefaultExtent(area, null, null, null); + } else { + extent.getGeographicElements().add(new DefaultGeographicDescription(area)); + } + } + /* + * Example: BBOX[51.43, 2.54, 55.77, 6.40] + */ + while ((element = parent.pullElement(OPTIONAL, WKTKeywords.BBox)) != null) { + final double southBoundLatitude = element.pullDouble("southBoundLatitude"); + final double westBoundLongitude = element.pullDouble("westBoundLongitude"); + final double northBoundLatitude = element.pullDouble("northBoundLatitude"); + final double eastBoundLongitude = element.pullDouble("eastBoundLongitude"); + element.close(ignoredElements); + if (extent == null) extent = new DefaultExtent(); + extent.getGeographicElements().add(new DefaultGeographicBoundingBox( + westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude)); + } + /* + * Example: VERTICALEXTENT[-1000, 0, LENGTHUNIT[“metre”, 1]] + * + * Units are optional, default to metres (no "contextual units" here). + */ + while ((element = parent.pullElement(OPTIONAL, WKTKeywords.VerticalExtent)) != null) { + final double minimum = element.pullDouble("minimum"); + final double maximum = element.pullDouble("maximum"); + Unit<Length> unit = parseScaledUnit(element, WKTKeywords.LengthUnit, Units.METRE); + element.close(ignoredElements); + if (unit == null) unit = Units.METRE; + if (extent == null) extent = new DefaultExtent(); + verticalElements = new VerticalInfo(verticalElements, extent, minimum, maximum, unit).resolve(verticalCRS); + } + /* + * Example: TIMEEXTENT[2013-01-01, 2013-12-31] + */ + while ((element = parent.pullElement(OPTIONAL, WKTKeywords.TimeExtent)) != null) { + if (element.peekValue() instanceof String) { + element.pullString("startTime"); + element.pullString("endTime"); + element.close(ignoredElements); + warning(parent, element, Errors.formatInternational(Errors.Keys.UnsupportedType_1, "TimeExtent[String,String]"), null); + } else { + final Temporal startTime = element.pullTime("startTime"); + final Temporal endTime = element.pullTime("endTime"); + element.close(ignoredElements); + final var t = new DefaultTemporalExtent(startTime, endTime); + if (extent == null) extent = new DefaultExtent(); + extent.getTemporalElements().add(t); + } + } + return extent; + } + /** * Parses an optional {@code "UNIT"} element of a known dimension. * This element has the following pattern: @@@ -1395,8 -1420,93 +1440,93 @@@ } /** - * Parses a {@code "Datum"} (WKT 2) element. The syntax is given by - * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#54">WKT 2 specification §8.2.4</a>. + * Parses a {@code "FrameEoch"} (WKT 2) element. + * + * @param parent the parent element. + * @return the frame epoch, or {@code null} if none. + * @throws ParseException if the {@code "FrameEoch"} element cannot be parsed. + */ + private Temporal parseDynamic(final Element parent) throws ParseException { + final Element element = parent.pullElement(OPTIONAL, WKTKeywords.Dynamic); + if (element == null) { + return null; + } + Temporal epoch = Epoch.fromYear(pullElementAsDouble(element, WKTKeywords.FrameEpoch, MANDATORY), 0); + element.close(ignoredElements); + return epoch; + } + + /** + * Parses an {@code "Ensemble"} (WKT 2) element. + * + * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. + * @param parent the parent element. + * @param datumType GeoAPI interface of the type of datum to create. + * @param meridian the prime meridian, or {@code null} if the ensemble is not geodetic. - * @return the {@code "Ensemble"} element as a {@link DatumEnsemble} object. ++ * @return the {@code "Ensemble"} element as a {@code DatumEnsemble} object. + * @throws ParseException if the {@code "Ensemble"} element cannot be parsed. + * + * @see org.apache.sis.referencing.datum.DefaultDatumEnsemble#formatTo(Formatter) + */ - private <D extends Datum> DatumEnsemble<D> parseEnsemble(final int mode, final Element parent, ++ private <D extends Datum> DefaultDatumEnsemble<D> parseEnsemble(final int mode, final Element parent, + final Class<D> datumType, final PrimeMeridian meridian) throws ParseException + { + final Element ensemble = parent.pullElement(mode, WKTKeywords.Ensemble); + if (ensemble == null) { + return null; + } + final String ensembleName = ensemble.pullString("name"); + final Ellipsoid ellipsoid; + final boolean vertical; + if (datumType == GeodeticDatum.class) { + ellipsoid = parseEllipsoid(MANDATORY, ensemble); + vertical = false; + } else if (datumType == Datum.class) { + // Unspecified datum type, use the presence of `ELLIPSOID` for detecting the geodetic case. + ellipsoid = parseEllipsoid(OPTIONAL, ensemble); + vertical = (ellipsoid == null); + } else { + ellipsoid = null; + vertical = (datumType == VerticalDatum.class); + } + final DatumFactory datumFactory = factories.getDatumFactory(); + final var members = new ArrayList<D>(); + try { + Element element = ensemble.pullElement(MANDATORY, WKTKeywords.Member); + do { + final Datum member; + try { + final String name = element.pullString("name"); + final Map<String,Object> properties = parseAnchorAndClose(element, name); + if (ellipsoid != null) { // `memberType` may be `Datum` or `GeodeticDatum` + member = datumFactory.createGeodeticDatum(properties, ellipsoid, meridian); + } else if (vertical) { - member = datumFactory.createVerticalDatum(properties, (RealizationMethod) null); ++ member = datumFactory.createVerticalDatum(properties, VerticalDatumTypes.fromDatum(name, null, null)); + } else if (datumType == TemporalDatum.class) { - member = datumFactory.createTemporalDatum(properties, (Temporal) null); - } else if (datumType == ParametricDatum.class) { - member = datumFactory.createParametricDatum(properties); ++ member = datumFactory.createTemporalDatum(properties, null); ++ } else if (datumType == DefaultParametricDatum.class) { ++ member = MissingMethods.createParametricDatum(properties, datumFactory); + } else if (datumType == EngineeringDatum.class) { + member = datumFactory.createEngineeringDatum(properties); + } else { + // Should never happen because this method is private and we checked all calls. + throw new AssertionError(datumType); + } + } catch (FactoryException exception) { + throw element.parseFailed(exception); + } + members.add(datumType.cast(member)); + element = ensemble.pullElement(OPTIONAL, WKTKeywords.Member); + } while (element != null); + var accuracy = PositionalAccuracyConstant.ensemble(pullElementAsDouble(ensemble, WKTKeywords.EnsembleAccuracy, MANDATORY)); - return datumFactory.createDatumEnsemble(parseAnchorAndClose(ensemble, ensembleName), members, accuracy); ++ return MissingMethods.createDatumEnsemble(parseAnchorAndClose(ensemble, ensembleName), members, accuracy, datumFactory); + } catch (FactoryException exception) { + throw ensemble.parseFailed(exception); + } + } + + /** + * Parses a {@code "Datum"} (WKT 2) element. * * The legacy WKT 1 pattern was: * @@@ -1429,7 -1542,10 +1562,10 @@@ } final DatumFactory datumFactory = factories.getDatumFactory(); try { - return datumFactory.createGeodeticDatum(properties, ellipsoid, meridian); + if (epoch == null) { + return datumFactory.createGeodeticDatum(properties, ellipsoid, meridian); + } - return datumFactory.createGeodeticDatum(properties, ellipsoid, meridian, epoch); ++ return MissingMethods.createGeodeticDatum(properties, ellipsoid, meridian, epoch, datumFactory); } catch (FactoryException exception) { throw element.parseFailed(exception); } @@@ -1469,9 -1586,13 +1606,13 @@@ if (method == null) { method = VerticalDatumTypes.fromDatum(name, null, null); } + final Map<String, Object> properties = parseAnchorAndClose(element, name); final DatumFactory datumFactory = factories.getDatumFactory(); try { - return datumFactory.createVerticalDatum(parseAnchorAndClose(element, name), method); + if (epoch == null) { + return datumFactory.createVerticalDatum(properties, method); + } - return datumFactory.createVerticalDatum(properties, method, epoch); ++ return MissingMethods.createVerticalDatum(properties, method, epoch, datumFactory); } catch (FactoryException exception) { throw element.parseFailed(exception); } @@@ -1494,13 -1617,19 +1637,19 @@@ if (element == null) { return null; } - final String name = element.pullString ("name"); - final Element origin = element.pullElement(MANDATORY, WKTKeywords.TimeOrigin); - final Instant epoch = origin .pullDate("origin"); - origin.close(ignoredElements); + final String name = element.pullString ("name"); + final Element origin = element.pullElement(OPTIONAL, WKTKeywords.TimeOrigin); + final Temporal epoch; + if (origin != null) { + epoch = origin .pullTime("origin"); + origin.close(ignoredElements); + } else { + // Default to the date of signing of the Convention du Mètre (from ISO 19162:2019 specification) + epoch = LocalDate.of(1875, 5, 20); + } final DatumFactory datumFactory = factories.getDatumFactory(); try { - return datumFactory.createTemporalDatum(parseAnchorAndClose(element, name), epoch); + return datumFactory.createTemporalDatum(parseAnchorAndClose(element, name), TemporalDate.toDate(epoch)); } catch (FactoryException exception) { throw element.parseFailed(exception); } @@@ -1629,6 -1756,7 +1775,7 @@@ * An EngineeringCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS. * In the latter case, the datum is null and we have instead DerivingConversion element from a base CRS. */ - DatumEnsemble<EngineeringDatum> ensemble = null; ++ DefaultDatumEnsemble<EngineeringDatum> ensemble = null; EngineeringDatum datum = null; SingleCRS baseCRS = null; Conversion fromBase = null; @@@ -1670,7 -1800,7 +1819,7 @@@ properties.put(Legacy.DERIVED_TYPE_KEY, EngineeringCRS.class); return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs); } - return crsFactory.createEngineeringCRS(properties, datum, cs); - return crsFactory.createEngineeringCRS(properties, datum, ensemble, cs); ++ return MissingMethods.createEngineeringCRS(properties, datum, ensemble, cs, crsFactory); } catch (FactoryException exception) { throw element.parseFailed(exception); } @@@ -1865,18 -1994,24 +2012,24 @@@ warning(element, WKTKeywords.AngleUnit, Errors.formatInternational( Errors.Keys.InconsistentUnitsForCS_1, angularUnit), null); } - final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, longitudeUnit); - final GeodeticDatum datum = parseDatum(MANDATORY, element, meridian); - final Map<String,?> properties = parseMetadataAndClose(element, name, datum); + PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, longitudeUnit); + if (meridian == null) { + meridian = greenwich(); + } + final Temporal epoch = parseDynamic(element); - final DatumEnsemble<GeodeticDatum> ensemble = parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian); ++ final DefaultDatumEnsemble<GeodeticDatum> ensemble = parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian); + final GeodeticDatum datum = parseDatum(ensemble == null ? MANDATORY : OPTIONAL, element, meridian, epoch); + final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; + final Map<String,?> properties = parseMetadataAndClose(element, name, datumOrEnsemble); if (cs instanceof EllipsoidalCS) { // By far the most frequent case. - return crsFactory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs); - return crsFactory.createGeographicCRS(properties, datum, ensemble, (EllipsoidalCS) cs); ++ return MissingMethods.createGeographicCRS(properties, datum, ensemble, (EllipsoidalCS) cs, crsFactory); } if (cs instanceof CartesianCS) { // The second most frequent case. - return crsFactory.createGeocentricCRS(properties, datum, - return crsFactory.createGeodeticCRS(properties, datum, ensemble, -- Legacy.forGeocentricCRS((CartesianCS) cs, false)); ++ return MissingMethods.createGeodeticCRS(properties, datum, ensemble, ++ Legacy.forGeocentricCRS((CartesianCS) cs, false), crsFactory); } if (cs instanceof SphericalCS) { // Not very common case. - return crsFactory.createGeocentricCRS(properties, datum, (SphericalCS) cs); - return crsFactory.createGeodeticCRS(properties, datum, ensemble, (SphericalCS) cs); ++ return MissingMethods.createGeodeticCRS(properties, datum, ensemble, (SphericalCS) cs, crsFactory); } } catch (FactoryException exception) { throw element.parseFailed(exception); @@@ -1918,6 -2052,7 +2070,7 @@@ * A VerticalCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind VerticalCRS. * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseVertCRS. */ - DatumEnsemble<VerticalDatum> ensemble = null; ++ DefaultDatumEnsemble<VerticalDatum> ensemble = null; VerticalDatum datum = null; SingleCRS baseCRS = null; Conversion fromBase = null; @@@ -1937,13 -2072,18 +2090,17 @@@ baseCRS = parseVerticalCRS(MANDATORY, element, true); } } - if (baseCRS == null) { // The most usual case. - datum = parseVerticalDatum(MANDATORY, element, isWKT1); + if (baseCRS == null) { // The most usual case. + final Temporal epoch = parseDynamic(element); + ensemble = parseEnsemble(OPTIONAL, element, VerticalDatum.class, null); + datum = parseVerticalDatum(ensemble == null ? MANDATORY : OPTIONAL, element, epoch, isWKT1); } + final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, false, datum.getVerticalDatumType()); - final Map<String,?> properties = parseMetadataAndClose(element, name, datum); - final RealizationMethod method = (datumOrEnsemble instanceof VerticalDatum) - ? ((VerticalDatum) datumOrEnsemble).getRealizationMethod().orElse(null) : null; ++ final var method = ((VerticalDatum) datumOrEnsemble).getVerticalDatumType(); + cs = parseCoordinateSystem(element, WKTKeywords.vertical, 1, isWKT1, unit, false, method); + final Map<String,?> properties = parseMetadataAndClose(element, name, datumOrEnsemble); if (cs instanceof VerticalCS) { final CRSFactory crsFactory = factories.getCRSFactory(); if (baseCRS != null) { @@@ -1953,15 -2093,18 +2110,18 @@@ * The `parseVerticalDatum(…)` method may have been unable to resolve the realization method. * But sometimes the axis (which was not available when we created the datum) provides * more information. Verify if we can have a better type now, and if so rebuild the datum. + * + * Note: we exclude the reconstruction of dynamic datum for simplicity in the use of the factory. + * This block is a dirty trick anyway (a workaround for a metadata excluded from the WKT standard). */ - if (datum.getVerticalDatumType() == VerticalDatumType.OTHER_SURFACE) { - if (method == null && datum != null && !(datum instanceof DynamicReferenceFrame)) { ++ if (method == VerticalDatumType.OTHER_SURFACE && datum != null && !(datum instanceof DefaultVerticalDatum.Dynamic)) { var type = VerticalDatumTypes.fromDatum(datum.getName().getCode(), datum.getAlias(), cs.getAxis(0)); - if (type != null) { + if (type != VerticalDatumType.OTHER_SURFACE) { final DatumFactory datumFactory = factories.getDatumFactory(); datum = datumFactory.createVerticalDatum(IdentifiedObjects.getProperties(datum), type); } } - verticalCRS = crsFactory.createVerticalCRS(properties, datum, (VerticalCS) cs); - verticalCRS = crsFactory.createVerticalCRS(properties, datum, ensemble, (VerticalCS) cs); ++ verticalCRS = MissingMethods.createVerticalCRS(properties, datum, ensemble, (VerticalCS) cs, crsFactory); /* * Some DefaultVerticalExtent objects may be waiting for the VerticalCRS before to complete * their construction. If this is the case, try to complete them now. @@@ -1999,6 -2142,7 +2159,7 @@@ * A TemporalCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind TemporalCRS. * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseTimeCRS. */ - DatumEnsemble<TemporalDatum> ensemble = null; ++ DefaultDatumEnsemble<TemporalDatum> ensemble = null; TemporalDatum datum = null; SingleCRS baseCRS = null; Conversion fromBase = null; @@@ -2030,7 -2176,7 +2193,7 @@@ if (baseCRS != null) { return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs); } - return crsFactory.createTemporalCRS(properties, datum, (TimeCS) cs); - return crsFactory.createTemporalCRS(properties, datum, ensemble, (TimeCS) cs); ++ return MissingMethods.createTemporalCRS(properties, datum, ensemble, (TimeCS) cs, crsFactory); } } catch (FactoryException exception) { throw element.parseFailed(exception); @@@ -2060,9 -2206,10 +2223,10 @@@ * A ParametricCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind ParametricCRS. * In the latter case, the datum is null and we have instead DerivingConversion element from a BaseParametricCRS. */ - DatumEnsemble<ParametricDatum> ensemble = null; - ParametricDatum datum = null; ++ DefaultDatumEnsemble<DefaultParametricDatum> ensemble = null; + DefaultParametricDatum datum = null; - SingleCRS baseCRS = null; - Conversion fromBase = null; + SingleCRS baseCRS = null; + Conversion fromBase = null; if (!isBaseCRS) { /* * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said @@@ -2079,19 -2226,21 +2243,21 @@@ baseCRS = parseParametricCRS(MANDATORY, element, true); } } - if (baseCRS == null) { // The most usual case. - datum = parseParametricDatum(MANDATORY, element); + if (baseCRS == null) { // The most usual case. - ensemble = parseEnsemble(OPTIONAL, element, ParametricDatum.class, null); ++ ensemble = parseEnsemble(OPTIONAL, element, DefaultParametricDatum.class, null); + datum = parseParametricDatum(ensemble == null ? MANDATORY : OPTIONAL, element); } + final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; final CoordinateSystem cs; try { cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, false, null); - final Map<String,?> properties = parseMetadataAndClose(element, name, datum); + final Map<String,?> properties = parseMetadataAndClose(element, name, datumOrEnsemble); - if (cs instanceof ParametricCS) { + if (cs instanceof DefaultParametricCS) { final CRSFactory crsFactory = factories.getCRSFactory(); if (baseCRS != null) { return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs); } - return MissingMethods.createParametricCRS(properties, datum, (DefaultParametricCS) cs, crsFactory); - return crsFactory.createParametricCRS(properties, datum, ensemble, (ParametricCS) cs); ++ return MissingMethods.createParametricCRS(properties, datum, ensemble, (DefaultParametricCS) cs, crsFactory); } } catch (FactoryException exception) { throw element.parseFailed(exception); @@@ -2260,10 -2407,12 +2424,12 @@@ buffer.append(number); axes[i] = csFactory.createCoordinateSystemAxis( singletonMap(CoordinateSystemAxis.NAME_KEY, buffer.toString()), - number, AxisDirection.UNSPECIFIED, Units.UNITY); + number, AxisDirections.UNSPECIFIED, Units.UNITY); } final Map<String,Object> properties = parseMetadataAndClose(element, name, baseCRS); - final Map<String,Object> axisName = singletonMap(CoordinateSystem.NAME_KEY, AxisDirections.appendTo(new StringBuilder("CS"), axes)); + final Map<String,Object> axisName = singletonMap( + CoordinateSystem.NAME_KEY, + AxisDirections.appendTo(new StringBuilder("CS"), axes)); final var derivedCS = new AbstractCS(axisName, axes); /* * Creates a derived CRS from the information found in a WKT 1 {@code FITTED_CS} element. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/MissingMethods.java index 091edd126e,0000000000..8b9e51db73 mode 100644,000000..100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/MissingMethods.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/pending/geoapi/referencing/MissingMethods.java @@@ -1,165 -1,0 +1,292 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.pending.geoapi.referencing; + +import java.util.Map; ++import java.util.Collection; +import java.util.function.Function; ++import java.time.temporal.Temporal; +import org.opengis.util.FactoryException; ++import org.opengis.referencing.cs.*; +import org.opengis.referencing.crs.*; +import org.opengis.referencing.datum.*; - import org.opengis.referencing.cs.CSFactory; - import org.opengis.referencing.cs.CoordinateSystemAxis; ++import org.opengis.metadata.quality.PositionalAccuracy; +import org.apache.sis.referencing.crs.DefaultVerticalCRS; +import org.apache.sis.referencing.crs.DefaultTemporalCRS; +import org.apache.sis.referencing.crs.DefaultEngineeringCRS; ++import org.apache.sis.referencing.crs.DefaultParametricCRS; +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.referencing.factory.GeodeticObjectFactory; - import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; + + +/** + * Placeholder for methods missing in the GeoAPI 3.0 interface. + */ +public final class MissingMethods { + /** + * To be set by static {@code AbstractCRS} initializer. + */ + public static volatile Function<CoordinateReferenceSystem, DefaultDatumEnsemble<?>> datumEnsemble; + + /** + * To be set by static {@code DefaultGeodeticCRS} initializer. + */ + public static volatile Function<GeodeticCRS, DefaultDatumEnsemble<GeodeticDatum>> geodeticDatumEnsemble; + + private MissingMethods() { + } + + /** + * Returns the datum ensemble of an arbitrary CRS. + * + * @param datum the CRS from which to get a datum ensemble, or {@code null} if none. + * @return the datum ensemble, or {@code null} if none. + */ + public static DefaultDatumEnsemble<?> getDatumEnsemble(final CoordinateReferenceSystem crs) { + final var m = datumEnsemble; + return (m != null) ? m.apply(crs) : null; + } + + /** + * Returns the datum ensemble of an arbitrary geodetic CRS. + * + * @param datum the CRS from which to get a datum ensemble, or {@code null} if none. + * @return the datum ensemble, or {@code null} if none. + */ + public static DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble(final GeodeticCRS crs) { + final var m = geodeticDatumEnsemble; + return (m != null) ? m.apply(crs) : null; + } + + /** + * Returns the datum ensemble of an arbitrary vertical CRS. + * + * @param datum the CRS from which to get a datum ensemble, or {@code null} if none. + * @return the datum ensemble, or {@code null} if none. + */ + public static DefaultDatumEnsemble<VerticalDatum> getDatumEnsemble(final VerticalCRS crs) { + return (crs instanceof DefaultVerticalCRS) ? ((DefaultVerticalCRS) crs).getDatumEnsemble() : null; + } + + /** + * Returns the datum ensemble of an arbitrary temporal CRS. + * + * @param datum the CRS from which to get a datum ensemble, or {@code null} if none. + * @return the datum ensemble, or {@code null} if none. + */ + public static DefaultDatumEnsemble<TemporalDatum> getDatumEnsemble(final TemporalCRS crs) { + return (crs instanceof DefaultTemporalCRS) ? ((DefaultTemporalCRS) crs).getDatumEnsemble() : null; + } + + /** + * Returns the datum ensemble of an arbitrary engineering CRS. + * + * @param datum the CRS from which to get a datum ensemble, or {@code null} if none. + * @return the datum ensemble, or {@code null} if none. + */ + public static DefaultDatumEnsemble<EngineeringDatum> getDatumEnsemble(final EngineeringCRS crs) { + return (crs instanceof DefaultEngineeringCRS) ? ((DefaultEngineeringCRS) crs).getDatumEnsemble() : null; + } + ++ /** ++ * Creates a datum ensemble. This method requires the <abbr>SIS</abbr> factory ++ * since datum ensembles were not available in GeoAPI 3.0. ++ * ++ * @param <D> the type of datum contained in the ensemble. ++ * @param properties name and other properties to give to the new object. ++ * @param members datum or reference frames which are members of the datum ensemble. ++ * @param accuracy inaccuracy introduced through use of the given collection of datums. ++ * @return the datum ensemble for the given properties. ++ * @throws FactoryException if the object creation failed. ++ */ ++ public static <D extends Datum> DefaultDatumEnsemble<D> createDatumEnsemble( ++ final Map<String,?> properties, ++ final Collection<? extends D> members, ++ final PositionalAccuracy accuracy, ++ DatumFactory factory) throws FactoryException ++ { ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createDatumEnsemble(properties, members, accuracy); ++ } ++ + /** + * Creates a parametric CS. This method requires the <abbr>SIS</abbr> factory + * since parametric CRS were not available in GeoAPI 3.0. + * + * @param properties the coordinate system name, and optionally other properties. + * @param axis the axis of the parametric coordinate system. + * @param factory the factory to use for creating the coordinate system. + * @return a parametric coordinate system using the given axes. + * @throws FactoryException if the parametric object creation failed. + */ + public static DefaultParametricCS createParametricCS(final Map<String,?> properties, final CoordinateSystemAxis axis, + CSFactory factory) throws FactoryException + { + if (!(factory instanceof GeodeticObjectFactory)) { + factory = GeodeticObjectFactory.provider(); + } + return ((GeodeticObjectFactory) factory).createParametricCS(properties, axis); + } + ++ /** ++ * Creates a parametric <abbr>CRS</abbr>. This method requires the <abbr>SIS</abbr> factory ++ * since parametric <abbr>CRS</abbr> were not available in GeoAPI 3.0. ++ * ++ * @param properties the coordinate reference system name, and optionally other properties. ++ * @param datum the parametric datum. ++ * @param cs the parametric coordinate system. ++ * @param factory the factory to use for creating the coordinate reference system. ++ * @return a parametric coordinate system using the given axes. ++ * @throws FactoryException if the parametric object creation failed. ++ */ ++ public static DefaultParametricCRS createParametricCRS(final Map<String,?> properties, final DefaultParametricDatum datum, ++ final DefaultParametricCS cs, CRSFactory factory) throws FactoryException ++ { ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createParametricCRS(properties, datum, cs); ++ } ++ + /** + * Creates a parametric datum. This method requires the <abbr>SIS</abbr> factory + * since parametric <abbr>CRS</abbr> were not available in GeoAPI 3.0. + * + * @param properties the datum name, and optionally other properties. + * @param factory the factory to use for creating the datum. + * @return a parametric datum using the given name. + * @throws FactoryException if the parametric object creation failed. + */ + public static DefaultParametricDatum createParametricDatum(final Map<String,?> properties, DatumFactory factory) + throws FactoryException + { + if (!(factory instanceof GeodeticObjectFactory)) { + factory = GeodeticObjectFactory.provider(); + } + return ((GeodeticObjectFactory) factory).createParametricDatum(properties); + } + - /** - * Creates a parametric <abbr>CRS</abbr>. This method requires the <abbr>SIS</abbr> factory - * since parametric <abbr>CRS</abbr> were not available in GeoAPI 3.0. - * - * @param properties the coordinate reference system name, and optionally other properties. - * @param datum the parametric datum. - * @param cs the parametric coordinate system. - * @param factory the factory to use for creating the coordinate reference system. - * @return a parametric coordinate system using the given axes. - * @throws FactoryException if the parametric object creation failed. - */ - public static SingleCRS createParametricCRS(final Map<String,?> properties, final DefaultParametricDatum datum, - final DefaultParametricCS cs, CRSFactory factory) throws FactoryException ++ public static VerticalDatum createVerticalDatum(final Map<String,?> properties, final VerticalDatumType type, ++ final Temporal epoch, DatumFactory factory) throws FactoryException ++ { ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createVerticalDatum(properties, type, epoch); ++ } ++ ++ public static GeodeticDatum createGeodeticDatum(final Map<String,?> properties, final Ellipsoid ellipsoid, ++ final PrimeMeridian primeMeridian, final Temporal epoch, DatumFactory factory) throws FactoryException + { + if (!(factory instanceof GeodeticObjectFactory)) { + factory = GeodeticObjectFactory.provider(); + } - try { - return ((GeodeticObjectFactory) factory).createParametricCRS(properties, datum, cs); - } catch (ClassCastException e) { - throw new InvalidGeodeticParameterException(e.toString(), e); ++ return ((GeodeticObjectFactory) factory).createGeodeticDatum(properties, ellipsoid, primeMeridian, epoch); ++ } ++ ++ public static GeographicCRS createGeographicCRS(final Map<String,?> properties, final GeodeticDatum datum, ++ final DefaultDatumEnsemble<GeodeticDatum> ensemble, final EllipsoidalCS cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (ensemble == null) { ++ return factory.createGeographicCRS(properties, datum, cs); ++ } ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createGeographicCRS(properties, datum, ensemble, cs); ++ } ++ ++ public static GeodeticCRS createGeodeticCRS(final Map<String,?> properties, final GeodeticDatum datum, ++ final DefaultDatumEnsemble<GeodeticDatum> ensemble, final SphericalCS cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (ensemble == null) { ++ return factory.createGeocentricCRS(properties, datum, cs); ++ } ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createGeodeticCRS(properties, datum, ensemble, cs); ++ } ++ ++ public static GeodeticCRS createGeodeticCRS(final Map<String,?> properties, final GeodeticDatum datum, ++ final DefaultDatumEnsemble<GeodeticDatum> ensemble, final CartesianCS cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (ensemble == null) { ++ return factory.createGeocentricCRS(properties, datum, cs); ++ } ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createGeodeticCRS(properties, datum, ensemble, cs); ++ } ++ ++ public static VerticalCRS createVerticalCRS(final Map<String,?> properties, final VerticalDatum datum, ++ final DefaultDatumEnsemble<VerticalDatum> ensemble, final VerticalCS cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (ensemble == null) { ++ return factory.createVerticalCRS(properties, datum, cs); ++ } ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createVerticalCRS(properties, datum, ensemble, cs); ++ } ++ ++ public static TemporalCRS createTemporalCRS(final Map<String,?> properties, final TemporalDatum datum, ++ final DefaultDatumEnsemble<TemporalDatum> ensemble, final TimeCS cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (ensemble == null) { ++ return factory.createTemporalCRS(properties, datum, cs); ++ } ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createTemporalCRS(properties, datum, ensemble, cs); ++ } ++ ++ public static DefaultParametricCRS createParametricCRS(final Map<String,?> properties, final DefaultParametricDatum datum, ++ final DefaultDatumEnsemble<DefaultParametricDatum> ensemble, final DefaultParametricCS cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); ++ } ++ return ((GeodeticObjectFactory) factory).createParametricCRS(properties, datum, ensemble, cs); ++ } ++ ++ public static EngineeringCRS createEngineeringCRS(final Map<String,?> properties, final EngineeringDatum datum, ++ final DefaultDatumEnsemble<EngineeringDatum> ensemble, final CoordinateSystem cs, CRSFactory factory) ++ throws FactoryException ++ { ++ if (ensemble == null) { ++ return factory.createEngineeringCRS(properties, datum, cs); ++ } ++ if (!(factory instanceof GeodeticObjectFactory)) { ++ factory = GeodeticObjectFactory.provider(); + } ++ return ((GeodeticObjectFactory) factory).createEngineeringCRS(properties, datum, ensemble, cs); + } +} diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java index 7a216d89fa,52b4d3ce8f..48721a0892 --- 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 @@@ -32,10 -33,12 +33,11 @@@ import org.opengis.referencing.crs.Coor import org.apache.sis.referencing.AbstractReferenceSystem; import org.apache.sis.referencing.cs.AbstractCS; import org.apache.sis.referencing.cs.AxesConvention; + import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis; import org.apache.sis.referencing.datum.AbstractDatum; - import org.apache.sis.referencing.privy.WKTUtilities; -import org.apache.sis.referencing.datum.DefaultDatumEnsemble; import org.apache.sis.referencing.privy.ReferencingUtilities; import org.apache.sis.metadata.privy.ImplementationHelper; + import org.apache.sis.io.wkt.FormattableObject; import org.apache.sis.io.wkt.Convention; import org.apache.sis.io.wkt.Formatter; import org.apache.sis.util.Utilities; @@@ -285,60 -286,6 +287,32 @@@ public class AbstractCRS extends Abstra return CoordinateReferenceSystem.class; } - /** - * Returns the datum or a view of the ensemble as a datum, or {@code null} if none. - * The {@code legacy} argument is usually {@code false}, except when formatting in a legacy <abbr>WKT</abbr> format. - * - * @param legacy whether to allow a view of the ensemble as a datum for interoperability with legacy standards. - * @return the datum, or {@code null} if none. - */ - Datum getDatumOrEnsemble(final boolean legacy) { - /* - * 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. - */ - if (this instanceof SingleCRS) { - final var crs = (SingleCRS) this; - final Datum datum = crs.getDatum(); - if (datum != null) { - return datum; - } - if (legacy) { - 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 = AbstractCRS::getDatumEnsemble; + } + /** * Returns the coordinate system. * @@@ -512,6 -456,56 +483,53 @@@ return keyword; } + /** + * Formats the datum or a view of the ensemble as a datum. + * Subclasses should override for invoking the static {@code formatDatum(…)} with more specifiy types. + */ + void formatDatum(final Formatter formatter) { + /* + * 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. + */ + if (this instanceof SingleCRS) { + final var sc = (SingleCRS) this; - formatDatum(formatter, sc, sc.getDatum(), AbstractDatum::castOrCopy, (crs) -> { - final DatumEnsemble<?> ensemble = crs.getDatumEnsemble(); - return (ensemble instanceof Datum) ? (Datum) ensemble : null; - }); ++ formatDatum(formatter, sc, sc.getDatum(), AbstractDatum::castOrCopy, AbstractCRS::getDatumEnsemble); + } + } + + /** + * Formats the datum or a view of the ensemble as a datum. + * + * @param <D> the type of datum or ensemble members. + * @param formatter the formatter where to write the datum. + * @param crs the coordinate reference system. + * @param datum the datum to format, or {@code null} if none. + * @param toFormattable the function to invoke for converting the datum to a formattable object. + * @param asDatum the function to invoke for getting an ensemble viewed as a datum. + */ + static <C extends SingleCRS, D extends Datum> void formatDatum( + final Formatter formatter, + final C crs, + final D datum, + final Function<D, FormattableObject> toFormattable, + final Function<C, D> asDatum) + { + final boolean supportsDynamic = formatter.getConvention().supports(Convention.WKT2_2019); + if (datum != null) { + if (supportsDynamic) { + formatter.append(DynamicCRS.createIfDynamic(datum)); + formatter.newLine(); + } + formatter.appendFormattable(datum, toFormattable); + } else if (supportsDynamic) { - formatter.appendFormattable(crs.getDatumEnsemble(), DefaultDatumEnsemble::castOrCopy); ++ formatter.append(getDatumEnsemble(crs)); + } else { + // Apply `toFormattable` unconditionally for forcing a conversion of ensemble to datum. + formatter.append(toFormattable.apply(asDatum.apply(crs))); + } + } + /** * Returns {@code true} if the given formatter is in the process of formatting the base CRS of an * {@link AbstractDerivedCRS}. In such case, the coordinate system axes shall not be formatted. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java index 40f51712e6,ad1d8ec2b7..6964465c93 --- 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 @@@ -429,22 -430,10 +429,10 @@@ public class DefaultDerivedCRS extends * @since 1.5 */ @Override - public DatumEnsemble<?> getDatumEnsemble() { - return getBaseCRS().getDatumEnsemble(); + public DefaultDatumEnsemble<?> getDatumEnsemble() { + 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); - } - /** * Returns the CRS on which the conversion is applied. * This CRS defines the {@linkplain #getDatum() datum} of this CRS and (at least implicitly) @@@ -691,11 -678,11 +677,6 @@@ return (GeodeticDatum) super.getDatum(); } - /** 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 the datum ensemble of the base geodetic CRS. */ - @Override public DatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return ((GeodeticCRS) getBaseCRS()).getDatumEnsemble(); -- } -- /** 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); @@@ -747,11 -737,11 +731,6 @@@ return (VerticalDatum) super.getDatum(); } - /** 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 datum ensemble of the base vertical CRS. */ - @Override public DatumEnsemble<VerticalDatum> getDatumEnsemble() { - return ((VerticalCRS) getBaseCRS()).getDatumEnsemble(); -- } -- /** Returns the coordinate system given at construction time. */ @Override public VerticalCS getCoordinateSystem() { return (VerticalCS) super.getCoordinateSystem(); @@@ -808,11 -798,11 +787,6 @@@ return (TemporalDatum) super.getDatum(); } - /** 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 datum ensemble of the base temporal CRS. */ - @Override public DatumEnsemble<TemporalDatum> getDatumEnsemble() { - return ((TemporalCRS) getBaseCRS()).getDatumEnsemble(); -- } -- /** Returns the coordinate system given at construction time. */ @Override public TimeCS getCoordinateSystem() { return (TimeCS) super.getCoordinateSystem(); @@@ -865,18 -855,18 +839,13 @@@ } /** 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 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. */ @@@ -933,11 -923,11 +902,6 @@@ return (EngineeringDatum) super.getDatum(); } - /** 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 the datum ensemble of the base engineering CRS. */ - @Override public DatumEnsemble<EngineeringDatum> getDatumEnsemble() { - return ((EngineeringCRS) getBaseCRS()).getDatumEnsemble(); -- } -- /** 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 2f6132417f,7071186029..be7105f12e --- 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 @@@ -43,12 -44,10 +44,11 @@@ import org.apache.sis.referencing.privy import org.apache.sis.util.resources.Errors; import org.apache.sis.io.wkt.Convention; import org.apache.sis.io.wkt.Formatter; - import org.apache.sis.measure.Units; -// 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; /** @@@ -161,23 -160,6 +161,14 @@@ 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 efb669c751,285fc6cb3e..44ce14eeec --- 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,13 -23,15 +23,12 @@@ 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.referencing.datum.DefaultParametricDatum; 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; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java index 5aa77cc06b,481f445d92..97b3a25f21 --- 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 @@@ -247,19 -246,10 +246,10 @@@ public class DefaultProjectedCRS extend * @since 1.5 */ @Override - public DatumEnsemble<GeodeticDatum> getDatumEnsemble() { - return getBaseCRS().getDatumEnsemble(); + public DefaultDatumEnsemble<GeodeticDatum> getDatumEnsemble() { + 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(); - } - /** * Returns the geographic CRS on which the map projection is applied. * This CRS defines the {@linkplain #getDatum() datum} of this CRS and (at least implicitly) diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java index 0000000000,23290ce2aa..1d87227402 mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DynamicCRS.java @@@ -1,0 -1,73 +1,77 @@@ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.sis.referencing.crs; + + import java.time.temporal.Temporal; + import org.opengis.referencing.datum.Datum; + import org.apache.sis.io.wkt.Formatter; + import org.apache.sis.io.wkt.FormattableObject; + import org.apache.sis.referencing.internal.Epoch; + import org.apache.sis.referencing.privy.WKTKeywords; + -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.DynamicReferenceFrame; ++// Specific to the main branch: ++import org.apache.sis.referencing.datum.DefaultGeodeticDatum; ++import org.apache.sis.referencing.datum.DefaultVerticalDatum; + + + /** + * An element inserted in the <abbr>WKT</abbr> formatting of dynamic <abbr>CRS</abbr>. + * + * @todo {@code MODEL} sub-element is not yet supported. + * + * @author Martin Desruisseaux (Geomatys) + */ + final class DynamicCRS extends FormattableObject { + /** + * The reference frame epoch. + */ + private final Temporal epoch; + + /** + * Creates a new element. + * + * @param epoch the reference frame epoch. + */ + private DynamicCRS(final Temporal epoch) { + this.epoch = epoch; + } + + /** + * Returns a {@code DYNAMIC} element for the given datum, or {@code null} if the datum is not dynamic. + */ + static DynamicCRS createIfDynamic(final Datum datum) { - if (datum instanceof DynamicReferenceFrame) { - return new DynamicCRS(((DynamicReferenceFrame) datum).getFrameReferenceEpoch()); ++ if (datum instanceof DefaultGeodeticDatum.Dynamic) { ++ return new DynamicCRS(((DefaultGeodeticDatum.Dynamic) datum).getFrameReferenceEpoch()); ++ } ++ if (datum instanceof DefaultVerticalDatum.Dynamic) { ++ return new DynamicCRS(((DefaultVerticalDatum.Dynamic) datum).getFrameReferenceEpoch()); + } + return null; + } + + /** + * Formats this epoch as a <i>Well Known Text</i> {@code CoordinateMetadata[…]} element. + * + * @param formatter the formatter where to format the inner content of this WKT element. + * @return {@code "Dynamic"}. + */ + @Override + protected String formatTo(final Formatter formatter) { + formatter.append(new Epoch(epoch, WKTKeywords.FrameEpoch)); + return WKTKeywords.Dynamic; + } + } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java index b8bb4f3b62,703457ad2b..f6585af5e3 --- 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 @@@ -44,12 -48,11 +48,11 @@@ import org.apache.sis.metadata.iso.cita import org.apache.sis.io.wkt.ElementKind; import org.apache.sis.io.wkt.Formatter; import static org.apache.sis.util.Utilities.deepEquals; - import static org.apache.sis.util.collection.Containers.property; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.Identifier; -import org.opengis.referencing.datum.DatumEnsemble; -import org.opengis.referencing.datum.DynamicReferenceFrame; +// Specific to the main branch: +import org.opengis.referencing.ReferenceIdentifier; +import org.opengis.metadata.extent.Extent; +import org.apache.sis.referencing.internal.Legacy; /** @@@ -623,6 -556,9 +634,9 @@@ public class AbstractDatum extends Abst } } formatter.append(name, ElementKind.DATUM); - if (formatter.getEnclosingElement(1) instanceof DatumEnsemble<?>) { ++ if (formatter.getEnclosingElement(1) instanceof DefaultDatumEnsemble<?>) { + return WKTKeywords.Member; + } return null; } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java index 4cc8d40079,fbead3b304..1a6d501a63 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java @@@ -146,10 -154,10 +149,10 @@@ public class DefaultDatumEnsemble<D ext * @param accuracy inaccuracy introduced through use of this ensemble (mandatory). * @param memberType the base type of datum members contained in this ensemble. * @throws ClassCastException if a member is not an instance of {@code memberType}. - * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two + * @throws IllegalArgumentException if a member is an instance of {@code DatumEnsemble}, of if at least two * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) */ protected DefaultDatumEnsemble(final Map<String,?> properties, final Collection<? extends D> members, @@@ -219,10 -228,11 +222,11 @@@ * Creates a datum ensemble from the given properties. The content of the {@code properties} map is described * {@linkplain #DefaultDatumEnsemble(Map, Collection, PositionalAccuracy, Class) in the constructor}. * The returned ensemble may implement the {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum}, -- * {@link ParametricDatum} or {@link EngineeringDatum} interface if all members are instances of the same interface. ++ * or {@link EngineeringDatum} interface if all members are instances of the same interface. * - * @param <D> the type of datum members contained in this ensemble. + * @param <D> the type of datum members contained in the ensemble to create. * @param properties the properties to be given to the identified object. + * @param memberType type of members, or {@code null} for automatic. * @param members datum or reference frames which are members of this ensemble. * @param accuracy inaccuracy introduced through use of this ensemble (mandatory). * @return the datum ensemble. @@@ -234,9 -245,40 +239,11 @@@ final Collection<? extends D> members, final PositionalAccuracy accuracy) { - return Factory.forMemberType(Datum.class, null, properties, List.copyOf(members), accuracy); + return Factory.forMemberType( + memberType != null ? memberType : Datum.class, + null, properties, List.copyOf(members), accuracy); } - /** - * Returns a SIS ensemble implementation with the values of the given arbitrary implementation. - * This method performs the first applicable action in the following choices: - * - * <ul> - * <li>If the given object is {@code null}, then this method returns {@code null}.</li> - * <li>Otherwise, if the given object is already an instance of - * {@code DefaultDatumEnsemble}, then it is returned unchanged.</li> - * <li>Otherwise, a new {@code DefaultDatumEnsemble} instance is created using the - * {@linkplain #DefaultDatumEnsemble(DatumEnsemble, Class) copy constructor} and returned. - * Note that this is a <em>shallow</em> copy operation, - * because the other properties contained in the given object are not recursively copied.</li> - * </ul> - * - * The returned ensemble may implement the {@link GeodeticDatum}, {@link VerticalDatum}, {@link TemporalDatum}, - * {@link ParametricDatum} or {@link EngineeringDatum} interface if all members are instances of the same interface. - * - * @param <D> the type of datum members contained in the ensemble. - * @param object the object to get as a SIS implementation, or {@code null} if none. - * @return a SIS implementation containing the values of the given object (may be the - * given object itself), or {@code null} if the argument was null. - */ - public static <D extends Datum> DefaultDatumEnsemble<D> castOrCopy(final DatumEnsemble<D> object) { - if (object == null || object instanceof DefaultDatumEnsemble<?>) { - return (DefaultDatumEnsemble<D>) object; - } - return Factory.forMemberType(Datum.class, object, null, List.copyOf(object.getMembers()), object.getEnsembleAccuracy()); - } - /** * Returns this datum ensemble as a collection of datum of the given type. * This method casts the datum members, it does not copy or rewrite them. @@@ -299,6 -360,31 +306,19 @@@ return ensembleAccuracy; } + /** + * Returns an estimation of positional accuracy in metres, or {@code NaN} if unknown. + * If at least one {@linkplain org.apache.sis.metadata.iso.quality.DefaultQuantitativeResult quantitative + * result} is found with a linear unit, then returns the largest result value converted to metres. + * + * @return the accuracy estimation (always in meters), or NaN if unknown. + * + * @see org.apache.sis.referencing.CRS#getLinearAccuracy(CoordinateOperation) + */ + public double getLinearAccuracy() { + return PositionalAccuracyConstant.getLinearAccuracy(CollectionsExt.singletonOrEmpty(getEnsembleAccuracy())); + } + - /** - * Returns an anchor definition which is common to all members of the datum ensemble. - * If the value is not the same for all members, or if at least one member returned - * an empty value, then this method returns an empty value. - * - * @return the common anchor definition, or empty if there is no common value. - */ - @Override - public Optional<InternationalString> getAnchorDefinition() { - return getCommonOptionalValue(Datum::getAnchorDefinition); - } - /** * Returns an anchor point which is common to all members of the datum ensemble. * If the value is not the same for all members, or if at least one member returned @@@ -460,7 -610,8 +496,7 @@@ check: if (it.hasNext()) /** * An ensemble viewed as a low-accuracy geodetic datum. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) - * @see #castOrCopy(DatumEnsemble) * @see DatumOrEnsemble#of(GeodeticCRS) * @see DatumOrEnsemble#asTargetDatum(GeodeticCRS, GeodeticCRS) */ @@@ -514,7 -679,8 +564,7 @@@ /** * An ensemble viewed as a low-accuracy vertical datum. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) - * @see #castOrCopy(DatumEnsemble) * @see DatumOrEnsemble#of(VerticalCRS) * @see DatumOrEnsemble#asTargetDatum(VerticalCRS, VerticalCRS) */ @@@ -554,7 -735,8 +604,7 @@@ /** * An ensemble viewed as a low-accuracy temporal datum. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) - * @see #castOrCopy(DatumEnsemble) * @see DatumOrEnsemble#of(TemporalCRS) * @see DatumOrEnsemble#asTargetDatum(TemporalCRS, TemporalCRS) */ @@@ -595,7 -777,37 +645,7 @@@ /** * An ensemble viewed as a low-accuracy engineering datum. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) - * @see #castOrCopy(DatumEnsemble) * @see DatumOrEnsemble#of(EngineeringCRS) * @see DatumOrEnsemble#asTargetDatum(EngineeringCRS, EngineeringCRS) */ @@@ -628,7 -840,8 +678,7 @@@ * * @param <D> base type of all members in the ensembles constructed by this factory instance. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) - * @see #castOrCopy(DatumEnsemble) */ private static abstract class Factory<D extends Datum> { /** @@@ -712,10 -925,10 +762,10 @@@ nextType: for (final Factory<?> facto * @param members datum or reference frames which are members of this ensemble. * @param accuracy inaccuracy introduced through use of this ensemble (mandatory). * @return the datum ensemble. - * @throws IllegalArgumentException if a member is an instance of {@link DatumEnsemble}, of if at least two + * @throws IllegalArgumentException if a member is an instance of {@code DatumEnsemble}, of if at least two * different {@linkplain AbstractDatum#getConventionalRS() conventional reference systems} are found. * - * @see #create(Map, Collection, PositionalAccuracy) + * @see #create(Map, Class, Collection, PositionalAccuracy) */ abstract DefaultDatumEnsemble<D> create(Map<String,?> properties, List<? extends D> members, PositionalAccuracy accuracy); @@@ -730,7 -945,7 +780,7 @@@ /** * Factories for all datum types supported by this class. The types are (in order) {@link GeodeticDatum}, -- * {@link VerticalDatum}, {@link TemporalDatum}, {@link ParametricDatum} and {@link EngineeringDatum}. ++ * {@link VerticalDatum}, {@link TemporalDatum} and {@link EngineeringDatum}. * The types are tested in iteration order. For example, if a member implements both {@code VerticalDatum} * {@code ParametricDatum} interface, then {@code VerticalDatum} has precedence. */ diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java index f70bdee1d0,7f5a051171..e2f1a12fde --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java @@@ -51,10 -51,10 +51,9 @@@ import org.apache.sis.io.wkt.Formatter import static org.apache.sis.util.Utilities.deepEquals; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement; - import static org.apache.sis.referencing.privy.WKTUtilities.toFormattable; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.Identifier; -import org.opengis.referencing.datum.DynamicReferenceFrame; +// Specific to the main branch: +import org.opengis.referencing.ReferenceIdentifier; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java index cc63fdbf93,e0efe9b6f9..98aeaf24a8 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultParametricDatum.java @@@ -128,14 -130,46 +128,15 @@@ public class DefaultParametricDatum ext } /** - * Returns a SIS datum implementation with the same values as the given arbitrary implementation. - * If the given object is {@code null}, then this method returns {@code null}. - * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged. - * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object. - * - * @param object the object to get as a SIS implementation, or {@code null} if none. - * @return a SIS implementation containing the values of the given object (may be the - * given object itself), or {@code null} if the argument was null. - */ - public static DefaultParametricDatum castOrCopy(final ParametricDatum object) { - return (object == null) || (object instanceof DefaultParametricDatum) ? - (DefaultParametricDatum) object : new DefaultParametricDatum(object); - } - - /** - * Returns the GeoAPI interface implemented by this class. - * The SIS implementation returns {@code ParametricDatum.class}. - * - * <h4>Note for implementers</h4> - * Subclasses usually do not need to override this method since GeoAPI does not define {@code TemporalDatum} - * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their - * own set of interfaces. - * - * @return {@code ParametricDatum.class} or a user-defined sub-interface. - */ - @Override - public Class<? extends ParametricDatum> getInterface() { - return ParametricDatum.class; - } - - /** - * Formats this datum as a <i>Well Known Text</i> {@code ParametricDatum[…]} element. + * Formats this datum as a <cite>Well Known Text</cite> {@code ParametricDatum[…]} element. * * <h4>Compatibility note</h4> - * {@code ParametricDatum} is defined in the WKT 2 specification only. + * {@code ParametricDatum} is defined in the <abbr>WKT</abbr> 2 specification only. + * Apache <abbr>SIS</abbr> accepts this type as members of datum ensembles, + * but this is not valid <abbr>WKT</abbr> according <abbr>ISO</abbr> 19162:2019. * - * @return {@code "ParametricDatum"}. - * - * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#83">WKT 2 specification</a> + * @return {@code "PDatum"} or {@code "ParametricDatum"}. + * May also be {@code "Member"} if this datum is inside a <abbr>WKT</abbr> {@code Ensemble[…]} element. */ @Override protected String formatTo(final Formatter formatter) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultVerticalDatum.java index 73bd9c4d48,4cb2b7369e..0094aab2db --- 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 @@@ -381,11 -441,10 +381,10 @@@ public class DefaultVerticalDatum exten * OGC 01-009 defined numerical codes for various vertical datum types, for example 2005 for geoidal height. * Such codes were formatted for all {@code Datum} subtypes in WKT 1. Datum types became specified only for * vertical datum in the ISO 19111:2003 standard, then removed completely in the ISO 19111:2007 standard. - * They were reintroduced in a different form ({@link RealizationMethod}) in the ISO 19111:2019 standard. + * They were reintroduced in a different form ({@code RealizationMethod}) in the ISO 19111:2019 standard. * - * @return {@code "VerticalDatum"} (WKT 2) or {@code "Vert_Datum"} (WKT 1). - * - * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#71">WKT 2 specification §10.2</a> + * @return {@code "VDatum"} or {@code "VerticalDatum"} (WKT 2), or {@code "Vert_Datum"} (WKT 1). + * May also be {@code "Member"} if this datum is inside a <abbr>WKT</abbr> {@code Ensemble[…]} element. */ @Override protected String formatTo(final Formatter formatter) { diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java index 60c2a6ed53,27e66e0b76..bcf2c18927 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultOperationMethod.java @@@ -511,10 -511,9 +511,8 @@@ public class DefaultOperationMethod ext * Formats this operation as a <i>Well Known Text</i> {@code Method[…]} element. * * @return {@code "Method"} (WKT 2) or {@code "Projection"} (WKT 1). - * - * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#118">WKT 2 specification §17.2.3</a> */ @Override - @SuppressWarnings("deprecation") protected String formatTo(final Formatter formatter) { final boolean isWKT1 = formatter.getConvention().majorVersion() == 1; /* diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java index a2398497f3,8993a6d39e..7d74482934 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/GeodeticObjectParserTest.java @@@ -55,6 -60,6 +60,12 @@@ import static org.apache.sis.referencin import static org.apache.sis.referencing.Assertions.assertDiagonalEquals; import static org.apache.sis.test.TestUtilities.getSingleton; ++// Specific to the main branch: ++import org.apache.sis.referencing.crs.DefaultGeographicCRS; ++import org.apache.sis.referencing.crs.DefaultProjectedCRS; ++import org.apache.sis.referencing.crs.DefaultVerticalCRS; ++import org.apache.sis.referencing.datum.DefaultVerticalDatum; ++ // Specific to the main and geoapi-3.1 branches: import org.apache.sis.temporal.TemporalDate; @@@ -481,6 -502,38 +508,38 @@@ public final class GeodeticObjectParser assertAxisEquals(AxisNames.GEODETIC_LONGITUDE, "λ", AxisDirection.EAST, -180, +180, Units.DEGREE, RangeMeaning.WRAPAROUND, cs.getAxis(1)); } + /** + * Tests the parsing of a geographic CRS with datum ensemble. + * + * @throws ParseException if the parsing failed. + */ + @Test + public void testGeographicCRSWithEnsemble() throws ParseException { - final GeographicCRS crs = parse(GeographicCRS.class, ++ final var crs = parse(DefaultGeographicCRS.class, + "GeodeticCRS[“WGS 84”,\n" + + " Ensemble[“World Geodetic System 1984”,\n" + + " Member[“World Geodetic System 1984 (Transit)”],\n" + + " Member[“World Geodetic System 1984 (G730)”],\n" + + " Member[“World Geodetic System 1984 (G873)”],\n" + + " Ellipsoid[“WGS84”, 6378137.0, 298.257223563],\n" + + " EnsembleAccuracy[2.0]],\n" + + " PrimeMeridian[“Greenwich”, 0],\n" + + " CS[ellipsoidal, 2],\n" + + " Axis[“Geodetic longitude (Lon)”, east],\n" + + " Axis[“Geodetic latitude (Lat)”, north],\n" + + " Unit[“degree”, 0.017453292519943295],\n" + + " Usage[\n" + + " Scope[“Horizontal component of 3D system.”],\n" + + " BBox[-90.00, -180.00, 90.00, 180.00]]]"); + verifyGeographicCRS(0, crs); + assertNull(crs.getDatum()); + final Iterator<GeodeticDatum> members = crs.getDatumEnsemble().getMembers().iterator(); + assertNameAndIdentifierEqual("World Geodetic System 1984 (Transit)", 0, members.next()); + assertNameAndIdentifierEqual("World Geodetic System 1984 (G730)", 0, members.next()); + assertNameAndIdentifierEqual("World Geodetic System 1984 (G873)", 0, members.next()); + assertFalse(members.hasNext()); + } + /** * Implementation of {@link #testGeographicCRS()} and related test methods. * This test expects no {@code AUTHORITY} element on any component. @@@ -731,8 -786,47 +792,47 @@@ " Scope[“Large and medium scale topographic mapping and engineering survey.”],\n" + " Id[“EPSG”, 27572, URI[“urn:ogc:def:crs:EPSG::27572”]]]"; -- final ProjectedCRS crs = parse(ProjectedCRS.class, wkt); ++ final var crs = parse(DefaultProjectedCRS.class, wkt); + validateParisFranceII(crs, 27572, false); + assertEquals("Large and medium scale topographic mapping and engineering survey.", + getSingleton(crs.getDomains()).getScope().toString()); + assertNull(getSingleton(crs.getIdentifiers()).getVersion(), "Identifier shall not have a version."); + } + + /** + * Tests the same <abbr>CRS</abbr> as {@link #testProjectedWithGradUnits()}, + * but from a string mostly conform to <abbr>ISO</abbr> 19162:2019. + * A small deviation is that this test includes accented letters. + * + * @throws ParseException if the parsing failed. + */ + @Test + public void testProjectedFromWKT2_2019() throws ParseException { + String wkt = "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" + + " BaseGeodCRS[“NTF (Paris)”,\n" + + " Datum[“Nouvelle Triangulation Française (Paris)”,\n" + + " Ellipsoid[“Clarke 1880 (IGN)”, 6378249.2, 293.4660212936269]],\n" + + " PrimeMeridian[“Paris”, 2.5969213, Unit[“grad”, 0.015707963267948967], Id[“EPSG”, 8903]],\n" + + " AngleUnit[“degree”, 0.017453292519943295]],\n" + + " Conversion[“Lambert zone II”,\n" + + " Method[“Lambert Conic Conformal (1SP)”],\n" + + " Parameter[“Latitude of natural origin”, 52.0, AngleUnit[“grad”, 0.015707963267948967]],\n" + + " Parameter[“Longitude of natural origin”, 0.0],\n" + + " Parameter[“Scale factor at natural origin”, 0.99987742],\n" + + " Parameter[“False easting”, 600.0, LengthUnit[“kilometre”, 1000]],\n" + + " Parameter[“False northing”, 2200.0, LengthUnit[“kilometre”, 1000]]],\n" + + " CS[Cartesian, 2],\n" + + " Axis[“Easting (E)”, east],\n" + + " Axis[“Northing (N)”, north],\n" + + " LengthUnit[“kilometre”, 1000],\n" + + " Usage[\n" + + " Scope[“Large and medium scale topographic mapping and engineering survey.”]],\n" + + " Id[“EPSG”, 27572, URI[“urn:ogc:def:crs:EPSG::27572”]]]"; + - final ProjectedCRS crs = parse(ProjectedCRS.class, wkt); ++ final var crs = parse(DefaultProjectedCRS.class, wkt); validateParisFranceII(crs, 27572, false); + assertEquals("Large and medium scale topographic mapping and engineering survey.", + getSingleton(crs.getDomains()).getScope().toString()); assertNull(getSingleton(crs.getIdentifiers()).getVersion(), "Identifier shall not have a version."); } @@@ -890,6 -984,35 +990,35 @@@ "AXIS[" + axis + "]]"); } + /** + * Tests the parsing of a vertical <abbr>CRS</abbr>. + * + * @throws ParseException if the parsing failed. + */ + @Test + public void testVerticalCRS() throws ParseException { - final VerticalCRS crs = parse(VerticalCRS.class, ++ final var crs = parse(DefaultVerticalCRS.class, + "VerticalCRS[“RH2000 height”,\n" + + " Dynamic[FrameEpoch[2000]],\n" + + " VerticalDatum[“Rikets hojdsystem 2000”],\n" + + " CS[vertical, 1],\n" + + " Axis[“Gravity-related height (H)”, up],\n" + + " Unit[“metre”, 1],\n" + + " Usage[\n" + + " Scope[“Geodesy, engineering survey.”],\n" + + " Area[“Sweden - onshore.”],\n" + + " BBox[55.28, 10.93, 69.07, 24.17]],\n" + + " Id[“EPSG”, 5613, “12.013”, URI[“urn:ogc:def:crs:EPSG:12.013:5613”]],\n" + + " Remark[“Replaces RH70 (CRS code 5718) from 2005.”]]"); + + assertNameAndIdentifierEqual("RH2000 height", 5613, crs); + assertNameAndIdentifierEqual("Rikets hojdsystem 2000", 0, crs.getDatum()); - Temporal epoch = assertInstanceOf(DynamicReferenceFrame.class, crs.getDatum()).getFrameReferenceEpoch(); ++ Temporal epoch = assertInstanceOf(DefaultVerticalDatum.Dynamic.class, crs.getDatum()).getFrameReferenceEpoch(); + assertEquals(Year.of(2000), epoch); + assertEquals("Geodesy, engineering survey.", getSingleton(crs.getDomains()).getScope().toString()); + assertEquals("Replaces RH70 (CRS code 5718) from 2005.", crs.getRemarks().toString()); + } + /** * Returns the conversion from {@code north} to {@code south}. */ diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java index ae7c354dc7,b230467515..4f6752af8c --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java @@@ -102,9 -74,9 +102,9 @@@ public final class DefaultVerticalDatum @Test public void testToWKT() { DefaultVerticalDatum datum; - datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Geoidal"), RealizationMethod.GEOID); + datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Geoidal"), VerticalDatumType.GEOIDAL); assertWktEquals(Convention.WKT1, "VERT_DATUM[“Geoidal”, 2005]", datum); - assertWktEquals(Convention.WKT2, "VDATUM[“Geoidal”]", datum); + assertWktEquals(Convention.WKT2_2015, "VDATUM[“Geoidal”]", datum); assertWktEquals(Convention.WKT2_SIMPLIFIED, "VerticalDatum[“Geoidal”]", datum); datum = new DefaultVerticalDatum(Map.of(DefaultVerticalDatum.NAME_KEY, "Ellipsoidal"), VerticalDatumTypes.ellipsoidal()); diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java index 03d9b0cfc8,487645a33a..a89761767b --- 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 @@@ -80,7 -81,7 +80,8 @@@ public final class ConsistencyTest exte * to rebuild the <abbr>CRS</abbr> at parsing time. */ private final String[] WKT_TO_IGNORE = { -- "VERTICALEXTENT" ++ "VERTICALEXTENT", ++ "VERT_DATUM" }; /**
