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 31beb1f10f7dc67b6a6ab6254b192966db3ec0a0 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Feb 24 11:02:20 2022 +0100 Add formatting support for ESRI WKT "GeogTran" element. --- .../apache/sis/io/wkt/GeodeticObjectParser.java | 4 +- .../operation/AbstractCoordinateOperation.java | 53 ++++++++++++++++++--- .../sis/io/wkt/GeodeticObjectParserTest.java | 3 +- .../java/org/apache/sis/io/wkt/WKTFormatTest.java | 55 +++++++++++++++++++--- 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java index 3ed1bf7..e47ebdf 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java @@ -2264,8 +2264,8 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo return null; } final String name = element.pullString("name"); - final CoordinateReferenceSystem sourceCRS = parseCoordinateReferenceSystem(element, true); - final CoordinateReferenceSystem targetCRS = parseCoordinateReferenceSystem(element, true); + final CoordinateReferenceSystem sourceCRS = parseGeodeticCRS(MANDATORY, element, 2, null); + final CoordinateReferenceSystem targetCRS = parseGeodeticCRS(MANDATORY, element, 2, null); final OperationMethod method = parseMethod(element, WKTKeywords.Method); final Map<String,Object> properties = parseParametersAndClose(element, name, method); try { diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java index 80eaa44..334951d 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java @@ -34,6 +34,7 @@ import org.opengis.metadata.Identifier; import org.opengis.metadata.extent.Extent; import org.opengis.metadata.quality.PositionalAccuracy; import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.GeneralDerivedCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.ConcatenatedOperation; @@ -71,6 +72,7 @@ import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.internal.util.UnmodifiableArrayList; import org.apache.sis.internal.system.Semaphores; import org.apache.sis.internal.system.Loggers; +import org.apache.sis.io.wkt.Convention; import static org.apache.sis.util.Utilities.deepEquals; @@ -103,7 +105,7 @@ import static org.apache.sis.util.Utilities.deepEquals; * synchronization. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 0.8 + * @version 1.2 * @since 0.6 * @module */ @@ -934,6 +936,17 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 /** * Formats this coordinate operation in Well Known Text (WKT) version 2 format. * + * <h4>ESRI extension</h4> + * Coordinate operations can not be formatted in standard WKT 1 format, but an ESRI variant of WKT 1 + * allows a subset of coordinate operations with the ESRI-specific {@code GEOGTRAN} keyword. + * To enabled this variant, {@link org.apache.sis.io.wkt.WKTFormat} can be configured as below: + * + * {@preformat java + * format = new WKTFormat(null, null); + * format.setConvention(Convention.WKT1_IGNORE_AXES); + * format.setNameAuthority(Citations.ESRI); + * } + * * @param formatter the formatter to use. * @return {@code "CoordinateOperation"}. * @@ -943,6 +956,10 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 protected String formatTo(final Formatter formatter) { super.formatTo(formatter); formatter.newLine(); + final CoordinateReferenceSystem sourceCRS = getSourceCRS(); + final CoordinateReferenceSystem targetCRS = getTargetCRS(); + final Convention convention = formatter.getConvention(); + final boolean isWKT1 = (convention.majorVersion() == 1); /* * If the WKT is a component of a ConcatenatedOperation, do not format the source CRS since it is identical * to the target CRS of the previous step, or to the source CRS of the enclosing "ConcatenatedOperation" if @@ -954,9 +971,18 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 final FormattableObject enclosing = formatter.getEnclosingElement(1); final boolean isSubOperation = (enclosing instanceof PassThroughOperation); final boolean isComponent = (enclosing instanceof ConcatenatedOperation); + boolean isGeogTran = false; if (!isSubOperation && !isComponent) { - append(formatter, getSourceCRS(), WKTKeywords.SourceCRS); - append(formatter, getTargetCRS(), WKTKeywords.TargetCRS); + isGeogTran = isWKT1 && (sourceCRS instanceof GeographicCRS) && (targetCRS instanceof GeographicCRS); + if (isGeogTran) { + // ESRI-specific, similar to WKT 1. + formatter.append(WKTUtilities.toFormattable(sourceCRS)); formatter.newLine(); + formatter.append(WKTUtilities.toFormattable(targetCRS)); formatter.newLine(); + } else { + // WKT 2 (ISO 19162). + append(formatter, sourceCRS, WKTKeywords.SourceCRS); + append(formatter, targetCRS, WKTKeywords.TargetCRS); + } } final OperationMethod method = getMethod(); if (method != null) { @@ -983,8 +1009,9 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 * to mix EPSG or someone else components with their own. Note also that we don't apply filtering * on MathTransform WKT neither for more reliable debugging. */ - final boolean filter = WKTUtilities.isEPSG(parameters.getDescriptor(), false) && // NOT method.getName() - Constants.EPSG.equalsIgnoreCase(Citations.toCodeSpace(formatter.getNameAuthority())); + final boolean filter = isGeogTran || + (WKTUtilities.isEPSG(parameters.getDescriptor(), false) && // NOT method.getName() + Constants.EPSG.equalsIgnoreCase(Citations.toCodeSpace(formatter.getNameAuthority()))); formatter.newLine(); formatter.indent(+1); for (final GeneralParameterValue param : parameters.values()) { @@ -995,7 +1022,10 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 formatter.indent(-1); } } - if (!isSubOperation && !(this instanceof ConcatenatedOperation)) { + /* + * Add interpolation CRS if we are formatting a top-level WKT 2 single operation. + */ + if (!isSubOperation && !isGeogTran && !(this instanceof ConcatenatedOperation)) { append(formatter, getInterpolationCRS(), WKTKeywords.InterpolationCRS); final double accuracy = getLinearAccuracy(); if (accuracy > 0) { @@ -1007,7 +1037,16 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 }); } } - if (formatter.getConvention().majorVersion() == 1) { + /* + * Verifies if what we wrote is allowed by the standard. + */ + if (isGeogTran) { + if (method == null || convention != Convention.WKT1_IGNORE_AXES) { + formatter.setInvalidWKT(this, null); + } + return WKTKeywords.GeogTran; + } + if (isWKT1) { formatter.setInvalidWKT(this, null); } if (isComponent) { diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java index e092f7f..822a0f0 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java @@ -1095,6 +1095,7 @@ public final strictfp class GeodeticObjectParserTest extends TestCase { * * @throws ParseException if the parsing failed. * + * @see <a href="https://issues.apache.org/jira/browse/SIS-538">SIS-538</a> * @since 1.2 */ @Test @@ -1116,7 +1117,7 @@ public final strictfp class GeodeticObjectParserTest extends TestCase { " PARAMETER[“Z_Axis_Translation”, 340.8944],\n" + " PARAMETER[“X_Axis_Rotation”, -8.001],\n" + " PARAMETER[“Y_Axis_Rotation”, -4.42],\n" + - " PARAMETER[“Z_Axis_Rotation”,-11.821],\n" + + " PARAMETER[“Z_Axis_Rotation”, -11.821],\n" + " PARAMETER[“Scale_Difference”, 1.0],\n" + " AUTHORITY[“EPSG”, 1074]]"); diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java index e81ed67..04c074a 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java @@ -36,7 +36,7 @@ import static org.apache.sis.test.Assert.*; * Tests {@link WKTFormat}. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.2 * @since 0.5 * @module */ @@ -143,7 +143,7 @@ public final strictfp class WKTFormatTest extends TestCase { * @throws ParseException if a parsing failed. */ private void testConsistency() throws ParseException { - testConsistency( + testConsistency(false, "GEOGCS[“Tokyo”," + "DATUM[“Tokyo”," + "SPHEROID[“Bessel 1841”, 6377397.155, 299.1528128, AUTHORITY[“EPSG”,“7004”]]," @@ -154,7 +154,7 @@ public final strictfp class WKTFormatTest extends TestCase { + "AXIS[“Long”,EAST]," + "AUTHORITY[“EPSG”,“4301”]]"); - testConsistency( + testConsistency(false, "GEOGCS[“NTF (Paris)”," + "DATUM[“Nouvelle_Triangulation_Francaise”," + "SPHEROID[“Clarke 1880 (IGN)”, 6378249.2, 293.466021293627, AUTHORITY[“EPSG”,“7011”]]," @@ -165,7 +165,7 @@ public final strictfp class WKTFormatTest extends TestCase { + "AXIS[“Long”,EAST]," + "AUTHORITY[“EPSG”,“4807”]]"); - testConsistency( + testConsistency(false, "PROJCS[“NAD27 / Texas South Central”," + "GEOGCS[“NAD27”," + "DATUM[“North American Datum 1927”," @@ -184,7 +184,7 @@ public final strictfp class WKTFormatTest extends TestCase { + "AXIS[“Y”,NORTH]," + "AXIS[“X”,EAST]]"); - testConsistency( + testConsistency(false, "VERT_CS[“mean sea level depth”," + "VERT_DATUM[“Mean Sea Level”,2005,AUTHORITY[“EPSG”,“5100”]]," + "UNIT[“kilometre”,1000],AXIS[“Z”,DOWN]]"); @@ -201,7 +201,7 @@ public final strictfp class WKTFormatTest extends TestCase { * @throws ParseException if a parsing failed. */ private void testConsistencyWithDenormalizedBaseCRS() throws ParseException { - testConsistency( + testConsistency(false, "PROJCS[“NTF (Paris) / France I”," + "GEOGCS[“NTF (Paris)”," + "DATUM[“Nouvelle_Triangulation_Francaise”," @@ -225,15 +225,56 @@ public final strictfp class WKTFormatTest extends TestCase { } /** + * Tests consistency between the parser and the formatter for the ESRI-specific "GeogTran" object. + * + * @throws ParseException if a parsing failed. + * + * @see <a href="https://issues.apache.org/jira/browse/SIS-538">SIS-538</a> + * @since 1.2 + */ + @Test + @DependsOnMethod("testConsistencyOfWKT1") + public void testConsistencyOfGeogTran() throws ParseException { + final Symbols symbols = new Symbols(Symbols.SQUARE_BRACKETS); + symbols.setPairedQuotes("“”", "\"\""); + format = new WKTFormat(null, null); + format.setConvention(Convention.WKT1_IGNORE_AXES); + format.setNameAuthority(Citations.ESRI); + format.setSymbols(symbols); + parser = format; + testConsistency(true, + "GEOGTRAN[“Abidjan 1987 to WGS 1984_20”,\n" + + " GEOGCS[“Abidjan 1987”,\n" + + " DATUM[“D_Abidjan_1987”,\n" + + " SPHEROID[“Clarke_ 880 RGS”, 6378249.145, 293.465]],\n" + + " PRIMEM[“Greenwich”, 0.0],\n" + + " UNIT[“degree”, 0.017453292519943295]],\n" + + " GEOGCS[“WGS 1984”,\n" + + " DATUM[“D_WGS_1984”,\n" + + " SPHEROID[“WGS 1984”, 6378137.0, 298.257223563]],\n" + + " PRIMEM[“Greenwich”, 0.0],\n" + + " UNIT[“degree”, 0.017453292519943295]],\n" + + " METHOD[“Geocentric_Translation”, AUTHORITY[“EPSG”, “9603”]],\n" + + " PARAMETER[“X-axis translation”, -123.1],\n" + + " PARAMETER[“Y-axis translation”, 53.2],\n" + + " PARAMETER[“Z-axis translation”, 465.4]]"); + } + + /** * Implementation of {@link #testConsistency()} for a single WKT. * + * @param strict whether to require strict equality of WKT strings. + * @param wkt the Well-Known Text to parse and reformat. * @throws ParseException if the parsing failed. */ - private void testConsistency(final String wkt) throws ParseException { + private void testConsistency(final boolean strict, final String wkt) throws ParseException { final Object expected = parser.parseObject(wkt); final String reformat = format.format(expected); final Object reparsed = format.parseObject(reformat); assertEqualsIgnoreMetadata(expected, reparsed); + if (strict) { + assertMultilinesEquals(wkt, reformat); + } } /**