This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 70af867f4ae0943997c8329382307de2e2681d5f Author: Martin Desruisseaux <[email protected]> AuthorDate: Sat Sep 6 11:34:42 2025 +0200 Complete the support of WKT 2 `USAGE` element and update the tests accordingly. This first simple WKT2:2019 element gives us a way to test the distinction between the two versions of WKT 2. --- .../org/apache/sis/console/CRSCommandTest.java | 7 +- .../main/org/apache/sis/io/wkt/Convention.java | 42 ++-- .../main/org/apache/sis/io/wkt/Formatter.java | 4 +- .../apache/sis/io/wkt/GeodeticObjectParser.java | 214 +++++++++++++-------- .../sis/referencing/DefaultObjectDomain.java | 1 + .../apache/sis/referencing/crs/AbstractCRS.java | 6 +- .../sis/referencing/crs/DefaultGeodeticCRS.java | 3 +- .../referencing/datum/DefaultGeodeticDatum.java | 3 +- .../apache/sis/referencing/privy/WKTUtilities.java | 63 ------ .../sis/io/wkt/GeodeticObjectParserTest.java | 47 ++++- .../referencing/AbstractReferenceSystemTest.java | 34 +++- .../referencing/crs/DefaultCompoundCRSTest.java | 5 +- .../referencing/crs/DefaultGeocentricCRSTest.java | 3 +- .../referencing/crs/DefaultGeographicCRSTest.java | 13 +- .../referencing/crs/DefaultProjectedCRSTest.java | 5 +- .../datum/DefaultGeodeticDatumTest.java | 7 +- 16 files changed, 260 insertions(+), 197 deletions(-) diff --git a/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java b/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java index fe2460df08..380b0b0c71 100644 --- a/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java +++ b/endorsed/src/org.apache.sis.console/test/org/apache/sis/console/CRSCommandTest.java @@ -50,9 +50,10 @@ public final class CRSCommandTest extends TestCase { " Axis[\"Latitude (B)\", north],\n" + " Axis[\"Longitude (L)\", east],\n" + " Unit[\"degree\", 0.017453292519943295],\n" + - "\\E(?: Scope\\[\".+\"\\],\n)?\\Q" + // Ignore SCOPE[…] if present. - " Area[\"\\E.*\\Q\"],\n" + // Language may vary because of SIS localization. - " BBox[-90.00, -180.00, 90.00, 180.00],\n" + + " Usage[\n" + + "\\E(?: Scope\\[\".+\"\\],\n)?\\Q" + // Ignore SCOPE[…] if present. + " Area[\"\\E.*\\Q\"],\n" + // Language may vary because of SIS localization. + " BBox[-90.00, -180.00, 90.00, 180.00]],\n" + " Id[\"EPSG\", 4326,\\E.*\\Q URI[\"urn:ogc:def:crs:EPSG:\\E.*\\Q:4326\"]]" + // Version number of EPSG dataset may vary. "\\E(?:,\n Remark\\[\".+\"\\])?\\]\n"; // Ignore trailing REMARK[…] if present. /* diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Convention.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Convention.java index d1b1163bf5..30b94cdca9 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Convention.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Convention.java @@ -67,7 +67,7 @@ public enum Convention { * <p>This is the default convention used by {@link FormattableObject#toWKT()} * and for new {@link WKTFormat} instances.</p> */ - WKT2(false, true, false), + WKT2(true, false), /** * The ISO 19162 format with omission of some optional elements. This convention is identical @@ -98,7 +98,7 @@ public enum Convention { * * <p>This is the default convention used by {@link FormattableObject#toString()}.</p> */ - WKT2_SIMPLIFIED(false, false, false), + WKT2_SIMPLIFIED(false, false), /** * The ISO 19162:2019 format, also known as “WKT 2”. @@ -110,7 +110,7 @@ public enum Convention { * * @since 1.5 */ - WKT2_2019(false, true, false), + WKT2_2019(true, false), /** * The ISO 19162:2015 format, also known as “WKT 2”. @@ -122,7 +122,7 @@ public enum Convention { * * @since 1.5 */ - WKT2_2015(false, true, false), + WKT2_2015(true, false), /** * The OGC 01-009 format, also known as “WKT 1”. @@ -160,7 +160,7 @@ public enum Convention { * </table> * </div></div> */ - WKT1(true, true, false), + WKT1(true, false), /** * The <cite>Simple Feature</cite> format, also known as “WKT 1”. @@ -177,7 +177,7 @@ public enum Convention { * (e.g. <q>meter</q> instead of <q>metre</q>).</li> * </ul> */ - WKT1_COMMON_UNITS(true, true, true), + WKT1_COMMON_UNITS(true, true), /** * The <cite>Simple Feature</cite> format without parsing of axis elements. @@ -194,7 +194,7 @@ public enum Convention { * * @since 0.6 */ - WKT1_IGNORE_AXES(true, true, true), + WKT1_IGNORE_AXES(true, true), /** * A special convention for formatting objects as stored internally by Apache SIS. @@ -220,18 +220,13 @@ public enum Convention { * This convention is used only for debugging purpose. */ @Debug - INTERNAL(false, false, false); + INTERNAL(false, false); /** * The default conventions. */ static final Convention DEFAULT = WKT2; - /** - * {@code true} for using WKT 1 syntax, or {@code false} for using WKT 2 syntax. - */ - private final boolean isWKT1; - /** * {@code true} for using short upper-case keywords by {@linkplain KeywordStyle#DEFAULT default}. */ @@ -253,12 +248,25 @@ public enum Convention { /** * Creates a new enumeration value. */ - private Convention(final boolean isWKT1, final boolean toUpperCase, final boolean usesCommonUnits) { - this.isWKT1 = isWKT1; + private Convention(final boolean toUpperCase, final boolean usesCommonUnits) { this.toUpperCase = toUpperCase; this.usesCommonUnits = usesCommonUnits; } + /** + * Returns whether this convention supports the feature of the given convention. + * For example, {@code supports(WKT2_2015)} returns {@code true} if this convention represents + * either <abbr>ISO</abbr> 19162:2015 or a more recent version such as <abbr>ISO</abbr> 19162:2019. + * + * @param base the base version which is required. + * @return whether this convention is the given base version or a more recent version. + * + * @since 1.5 + */ + public boolean supports(final Convention base) { + return compareTo(base) <= 0 || this == INTERNAL; + } + /** * Returns the major version of the Well Known Text represented by this convention. * In current Apache SIS implementation, this method can return only 1 or 2. @@ -266,7 +274,7 @@ public enum Convention { * @return 1 if this convention is one of the WKT 1 variants, or 2 otherwise. */ public int majorVersion() { - return isWKT1 ? 1 : 2; + return supports(WKT2_2015) ? 2 : 1; } /** @@ -303,6 +311,6 @@ public enum Convention { * @see Citations#OGC */ final Citation getNameAuthority() { - return isWKT1 ? Citations.OGC : Citations.EPSG; + return majorVersion() == 1 ? Citations.OGC : Citations.EPSG; } } diff --git 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 index 80fa8ce554..4a8ec2309f 100644 --- 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 @@ -906,11 +906,11 @@ public class Formatter implements Localized { return; } appendOnNewLine(WKTKeywords.Anchor, anchor, null); - final boolean usage = convention.compareTo(Convention.WKT2_2015) < 0 - && convention != Convention.WKT2_SIMPLIFIED; // TODO: remove that exclusion. + final boolean usage = convention.supports(Convention.WKT2_2019); for (final ObjectDomain domain : object.getDomains()) { if (usage) { // ISO 19162:2019 + newLine(); appendFormattable(domain, DefaultObjectDomain::castOrCopy); } else { // ISO 19162:2015 diff --git 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 index 0de3f4ed76..8e9e9d85fa 100644 --- 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 @@ -54,12 +54,14 @@ import org.apache.sis.measure.UnitFormat; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.ImmutableIdentifier; +import org.apache.sis.referencing.DefaultObjectDomain; import org.apache.sis.referencing.cs.AbstractCS; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.crs.DefaultDerivedCRS; import org.apache.sis.referencing.datum.BursaWolfParameters; import org.apache.sis.referencing.datum.DefaultGeodeticDatum; import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory; +import org.apache.sis.referencing.operation.provider.AbstractProvider; import org.apache.sis.referencing.privy.CoordinateOperations; import org.apache.sis.referencing.privy.ReferencingFactoryContainer; import org.apache.sis.referencing.privy.EllipsoidalHeightCombiner; @@ -76,7 +78,6 @@ import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription; import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent; import org.apache.sis.metadata.privy.AxisNames; -import org.apache.sis.referencing.operation.provider.AbstractProvider; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.privy.Constants; import org.apache.sis.util.privy.Numerics; @@ -464,94 +465,45 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo // 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(ObjectDomain.SCOPE_KEY, element.pullString("scope")); - 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>(); + while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Usage)) != null) { + final String scope = parseScope(element); + 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 = parseScope(parent); + if (scope != null) { + properties.put(ObjectDomain.SCOPE_KEY, scope); } + final DefaultExtent extent = parseExtent(parent); if (extent != null) { properties.put(ObjectDomain.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)); + } + /* + * 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) { @@ -574,6 +526,108 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo return properties; } + /** + * Parses the {@code SCOPE} element. + * This element has the following pattern: + * + * {@snippet lang="wkt" : + * SCOPE["Large scale topographic mapping and cadastre."] + * } + * + * @param parent the parent element. + * @return the scope, or {@code null} if none. + * @throws ParseException if an element cannot be parsed. + */ + private String parseScope(final Element parent) throws ParseException { + final Element element = parent.pullElement(OPTIONAL, WKTKeywords.Scope); + if (element != null) { + final String scope = element.pullString("scope"); + element.close(ignoredElements); + return scope; + } + return null; + } + + /** + * 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}. + * 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 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); + } + } + return extent; + } + /** * Parses an optional {@code "UNIT"} element of a known dimension. * This element has the following pattern: diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java index 4131570f6b..bb2cd1bf2c 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/DefaultObjectDomain.java @@ -289,6 +289,7 @@ public class DefaultObjectDomain extends FormattableObject implements ObjectDoma */ @Override protected String formatTo(final Formatter formatter) { + formatter.newLine(); // Use the fields directly in order to keep null values. formatter.append(scope, domainOfValidity); return WKTKeywords.Usage; diff --git 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 index 698925cc67..184d2b5ae7 100644 --- 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,8 +32,8 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; 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.privy.ReferencingUtilities; import org.apache.sis.metadata.privy.ImplementationHelper; import org.apache.sis.io.wkt.Convention; @@ -523,7 +523,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe } } else { // WKT2 only, since the concept of CoordinateSystem was not explicit in WKT 1. - formatter.append(WKTUtilities.toFormattable(cs)); + formatter.appendFormattable(cs, AbstractCS::castOrCopy); formatter.indent(+1); } if (!isWKT1 || formatter.getConvention() != Convention.WKT1_IGNORE_AXES) { @@ -532,7 +532,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe final int dimension = cs.getDimension(); for (int i=0; i<dimension; i++) { formatter.newLine(); - formatter.append(WKTUtilities.toFormattable(cs.getAxis(i))); + formatter.appendFormattable(cs.getAxis(i), DefaultCoordinateSystemAxis::castOrCopy); } } } diff --git 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 index 04ef599736..93844e7a95 100644 --- 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 @@ -35,6 +35,7 @@ import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.cs.AbstractCS; import org.apache.sis.referencing.datum.DatumOrEnsemble; import org.apache.sis.referencing.datum.DefaultGeodeticDatum; +import org.apache.sis.referencing.datum.DefaultPrimeMeridian; import org.apache.sis.referencing.internal.Legacy; import org.apache.sis.referencing.privy.AxisDirections; import org.apache.sis.referencing.privy.WKTKeywords; @@ -232,7 +233,7 @@ class DefaultGeodeticCRS extends AbstractSingleCRS<GeodeticDatum> implements Geo { final Unit<Angle> oldUnit = formatter.addContextualUnit(angularUnit); formatter.indent(1); - formatter.append(WKTUtilities.toFormattable(pm)); + formatter.appendFormattable(pm, DefaultPrimeMeridian::castOrCopy); formatter.indent(-1); formatter.newLine(); formatter.restoreContextualUnit(angularUnit, oldUnit); diff --git 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 index f3810b4eca..3e00f7e85c 100644 --- 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,7 +51,6 @@ 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; @@ -746,7 +745,7 @@ public class DefaultGeodeticDatum extends AbstractDatum implements GeodeticDatum protected String formatTo(final Formatter formatter) { super.formatTo(formatter); formatter.newLine(); - formatter.append(toFormattable(getEllipsoid())); + formatter.appendFormattable(getEllipsoid(), DefaultEllipsoid::castOrCopy); final boolean isWKT1 = formatter.getConvention().majorVersion() == 1; if (isWKT1) { /* diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java index 4ebc07ed6c..7f118cc5f7 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/WKTUtilities.java @@ -29,18 +29,11 @@ import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.CoordinateSystem; -import org.opengis.referencing.cs.CoordinateSystemAxis; -import org.opengis.referencing.datum.PrimeMeridian; -import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.crs.AbstractCRS; -import org.apache.sis.referencing.cs.AbstractCS; -import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis; import org.apache.sis.referencing.datum.DatumOrEnsemble; -import org.apache.sis.referencing.datum.DefaultPrimeMeridian; -import org.apache.sis.referencing.datum.DefaultEllipsoid; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.operation.provider.Affine; import org.apache.sis.system.Loggers; @@ -132,62 +125,6 @@ public final class WKTUtilities extends Static { } } - /** - * Returns the given coordinate system as a formattable object. - * - * @param object the coordinate system, or {@code null}. - * @return the given coordinate system as a formattable object, or {@code null}. - */ - public static FormattableObject toFormattable(final CoordinateSystem object) { - if (object instanceof FormattableObject) { - return (FormattableObject) object; - } else { - return AbstractCS.castOrCopy(object); - } - } - - /** - * Returns the given coordinate system axis as a formattable object. - * - * @param object the coordinate system axis, or {@code null}. - * @return the given coordinate system axis as a formattable object, or {@code null}. - */ - public static FormattableObject toFormattable(final CoordinateSystemAxis object) { - if (object instanceof FormattableObject) { - return (FormattableObject) object; - } else { - return DefaultCoordinateSystemAxis.castOrCopy(object); - } - } - - /** - * Returns the ellipsoid as a formattable object. - * - * @param object the ellipsoid, or {@code null}. - * @return the given ellipsoid as a formattable object, or {@code null}. - */ - public static FormattableObject toFormattable(final Ellipsoid object) { - if (object instanceof FormattableObject) { - return (FormattableObject) object; - } else { - return DefaultEllipsoid.castOrCopy(object); - } - } - - /** - * Returns the given prime meridian as a formattable object. - * - * @param object the prime meridian, or {@code null}. - * @return the given prime meridian as a formattable object, or {@code null}. - */ - public static FormattableObject toFormattable(final PrimeMeridian object) { - if (object instanceof FormattableObject) { - return (FormattableObject) object; - } else { - return DefaultPrimeMeridian.castOrCopy(object); - } - } - /** * Converts the given object in a {@code FormattableObject} instance. Callers should verify that the * given object is not already an instance of {@code FormattableObject} before to invoke this method. diff --git 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 index d07e3cbe01..5ad05410be 100644 --- 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 @@ -484,6 +484,7 @@ public final class GeodeticObjectParserTest extends EPSGDependentTestCase { * * @param swap 1 if axes are expected to be swapped, or 0 otherwise. */ + @SuppressWarnings("PointlessBitwiseExpression") private void verifyGeographicCRS(final int swap, final GeographicCRS crs) throws ParseException { assertNameAndIdentifierEqual("WGS 84", 0, crs); @@ -698,8 +699,9 @@ public final class GeodeticObjectParserTest extends EPSGDependentTestCase { } /** - * Tests the same CRS as {@link #testProjectedWithGradUnits()}, but from a WKT 2 definition - * (except for inclusion of accented letters). + * Tests the same <abbr>CRS</abbr> as {@link #testProjectedWithGradUnits()}, + * but from a string mostly conform to <abbr>ISO</abbr> 19162:2015. + * A small deviation is that this test includes accented letters. * * @throws ParseException if the parsing failed. * @@ -707,7 +709,7 @@ public final class GeodeticObjectParserTest extends EPSGDependentTestCase { * @see <a href="https://issues.apache.org/jira/browse/SIS-310">SIS-310</a> */ @Test - public void testProjectedFromWKT2() throws ParseException { + public void testProjectedFromWKT2_2015() throws ParseException { String wkt = "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" + " BaseGeodCRS[“NTF (Paris)”,\n" + " Datum[“Nouvelle Triangulation Française (Paris)”,\n" + @@ -730,6 +732,45 @@ public final class GeodeticObjectParserTest extends EPSGDependentTestCase { final ProjectedCRS crs = parse(ProjectedCRS.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],\n" + + " Parameter[“False northing”, 2200.0]],\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); + 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."); } diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java index e0865ba8f9..cd16bd8426 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java @@ -137,24 +137,38 @@ public final class AbstractReferenceSystemTest extends TestCase { " REMARK[“注です。”]]", object); + assertWktEquals(Convention.WKT2_2019, + "ReferenceSystem[“My \"object\".”,\n" + // Quotes replaced + " USAGE[\n" + + " SCOPE[“Large scale topographic mapping and cadastre.”],\n" + + " AREA[“Netherlands offshore.”],\n" + + " BBOX[51.43, 2.54, 55.77, 6.40],\n" + + " VERTICALEXTENT[-1000, -10, LENGTHUNIT[“metre”, 1]],\n" + + " TIMEEXTENT[2010-04-05, 2010-09-08]],\n" + + " ID[“EPSG”, 4326, “8.2”, URI[“urn:ogc:def:referenceSystem:EPSG:8.2:4326”]],\n" + + " REMARK[“注です。”]]", + object); + assertWktEquals(Convention.WKT2_SIMPLIFIED, "ReferenceSystem[“My \"object\".”,\n" + - " Scope[“Large scale topographic mapping and cadastre.”],\n" + - " Area[“Netherlands offshore.”],\n" + - " BBox[51.43, 2.54, 55.77, 6.40],\n" + - " VerticalExtent[-1000, -10],\n" + - " TimeExtent[2010-04-05, 2010-09-08],\n" + + " Usage[\n" + + " Scope[“Large scale topographic mapping and cadastre.”],\n" + + " Area[“Netherlands offshore.”],\n" + + " BBox[51.43, 2.54, 55.77, 6.40],\n" + + " VerticalExtent[-1000, -10],\n" + + " TimeExtent[2010-04-05, 2010-09-08]],\n" + " Id[“EPSG”, 4326, “8.2”, URI[“urn:ogc:def:referenceSystem:EPSG:8.2:4326”]],\n" + " Remark[“注です。”]]", object); assertWktEquals(Convention.INTERNAL, "ReferenceSystem[“My “object””.”,\n" + // Quote doubled - " Scope[“Large scale topographic mapping and cadastre.”],\n" + - " Area[“Netherlands offshore.”],\n" + - " BBox[51.43, 2.54, 55.77, 6.40],\n" + - " VerticalExtent[-1000, -10],\n" + - " TimeExtent[2010-04-05, 2010-09-08],\n" + + " Usage[\n" + + " Scope[“Large scale topographic mapping and cadastre.”],\n" + + " Area[“Netherlands offshore.”],\n" + + " BBox[51.43, 2.54, 55.77, 6.40],\n" + + " VerticalExtent[-1000, -10],\n" + + " TimeExtent[2010-04-05, 2010-09-08]],\n" + " Id[“EPSG”, 4326, “8.2”],\n" + " Remark[“注です。”]]", object); diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java index d3ff22223e..2d78dbffd9 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java @@ -312,8 +312,9 @@ public final class DefaultCompoundCRSTest extends TestCase { " CS[temporal, 1],\n" + " Axis[“Time (t)”, future],\n" + " TimeUnit[“day”, 86400]],\n" + - " Area[“World”],\n" + - " BBox[-90.00, -180.00, 90.00, 180.00]]", + " Usage[\n" + + " Area[“World”],\n" + + " BBox[-90.00, -180.00, 90.00, 180.00]]]", HardCodedCRS.GEOID_4D); } diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeocentricCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeocentricCRSTest.java index 07adc06e9f..11bac83880 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeocentricCRSTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeocentricCRSTest.java @@ -172,7 +172,8 @@ public final class DefaultGeocentricCRSTest extends TestCase { "GeodeticCRS[“Geocentric”,\n" + " Datum[“World Geodetic System 1984”,\n" + " Ellipsoid[“WGS84”, 6378137.0, 298.257223563],\n" + - " Scope[“Satellite navigation.”],\n" + + " Usage[\n" + + " Scope[“Satellite navigation.”]],\n" + " Id[“EPSG”, 6326]],\n" + " PrimeMeridian[“Greenwich”, 0.0, Id[“EPSG”, 8901]],\n" + " CS[Cartesian, 3],\n" + diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java index 4f49d51bd7..931da16470 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java @@ -204,8 +204,9 @@ public final class DefaultGeographicCRSTest extends TestCase { " Axis[“Longitude (L)”, east],\n" + " Axis[“Latitude (B)”, north],\n" + " Unit[“degree”, 0.017453292519943295],\n" + - " Area[“World”],\n" + - " BBox[-90.00, -180.00, 90.00, 180.00]]", + " Usage[\n" + + " Area[“World”],\n" + + " BBox[-90.00, -180.00, 90.00, 180.00]]]", HardCodedCRS.WGS84); } @@ -218,15 +219,17 @@ public final class DefaultGeographicCRSTest extends TestCase { "GeodeticCRS[“WGS 84”,\n" + " Datum[“World Geodetic System 1984”,\n" + " Ellipsoid[“WGS84”, 6378137.0, 298.257223563],\n" + - " Scope[“Satellite navigation.”],\n" + + " Usage[\n" + + " Scope[“Satellite navigation.”]],\n" + " Id[“EPSG”, 6326]],\n" + " PrimeMeridian[“Greenwich”, 0.0, Id[“EPSG”, 8901]],\n" + " CS[ellipsoidal, 2],\n" + " Axis[“Geodetic longitude (λ)”, east],\n" + " Axis[“Geodetic latitude (φ)”, north],\n" + " Unit[“degree”, 0.017453292519943295, Id[“EPSG”, 9102]],\n" + - " Area[“World”],\n" + - " BBox[-90.00, -180.00, 90.00, 180.00]]", + " Usage[\n" + + " Area[“World”],\n" + + " BBox[-90.00, -180.00, 90.00, 180.00]]]", HardCodedCRS.WGS84); } diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java index 5c1ab298ad..faf4f9e82a 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java @@ -262,14 +262,15 @@ public final class DefaultProjectedCRSTest extends TestCase.WithLogs { * @throws FactoryException if the CRS creation failed. */ @Test - public void testInternal() throws FactoryException { + public void testWKT2_Internal() throws FactoryException { ProjectedCRS crs = create(HardCodedCRS.NTF); assertWktEquals(Convention.INTERNAL, "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" + " BaseGeodCRS[“NTF (Paris)”,\n" + " Datum[“Nouvelle Triangulation Française”,\n" + " Ellipsoid[“NTF”, 6378249.2, 293.4660212936269],\n" + - " Scope[“Topographic mapping.”],\n" + + " Usage[\n" + + " Scope[“Topographic mapping.”]],\n" + " Id[“EPSG”, 6807]],\n" + " PrimeMeridian[“Paris”, 2.5969213, Id[“EPSG”, 8903]],\n" + " CS[ellipsoidal, 2],\n" + diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java index 3c116c9930..a1445d78dd 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java @@ -343,9 +343,10 @@ public final class DefaultGeodeticDatumTest extends TestCase { " Ellipsoid[“WGS 84”, 6378137.0, 298.257223563, Id[“EPSG”, 7030],\n" + " Remark[“Defining parameters cited in EPSG database.”]],\n" + " Anchor[“Station coordinates changed by a few centimetres in 1994, 1997, 2002 and 2012.”],\n" + - " Scope[“Satellite navigation.”],\n" + - " Area[“World.”],\n" + - " BBox[-90.00, -180.00, 90.00, 180.00],\n" + + " Usage[\n" + + " Scope[“Satellite navigation.”],\n" + + " Area[“World.”],\n" + + " BBox[-90.00, -180.00, 90.00, 180.00]],\n" + " Id[“EPSG”, 6326],\n" + " Remark[“No distinction between the original and subsequent WGS 84 frames.”]]", datum);
