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 85dd6fbbf2affca8d4473f160195171f589e004b Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Sep 10 11:37:03 2025 +0200 Fix wrong formatting of concatenated operation, and add parsing support. --- .../apache/sis/io/wkt/GeodeticObjectParser.java | 90 ++++++++++------ .../operation/AbstractCoordinateOperation.java | 16 +-- .../operation/DefaultConcatenatedOperation.java | 32 ++++-- .../DefaultCoordinateOperationFactory.java | 40 ++++++- .../DefaultConcatenatedOperationTest.java | 119 +++++++++++---------- 5 files changed, 185 insertions(+), 112 deletions(-) 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 93742a1ddf..a712ecbbfa 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 @@ -318,19 +318,19 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo Object object; if (null == (object = parseCoordinateReferenceSystem(element, false)) && null == (object = parseCoordinateMetadata(FIRST, element)) - && null == (object = parseMathTransform (element, false)) - && null == (object = parseAxis (FIRST, element, null, Units.METRE )) - && null == (object = parsePrimeMeridian (FIRST, element, false, Units.DEGREE)) - && null == (object = parseEnsemble (FIRST, element, Datum.class, greenwich())) + && null == (object = parseOperation (FIRST, element)) + && null == (object = parseMathTransform ( element, false)) + && null == (object = parseEnsemble (FIRST, element, greenwich(), Datum.class)) && null == (object = parseDatum (FIRST, element, greenwich(), null)) - && null == (object = parseEllipsoid (FIRST, element)) - && null == (object = parseToWGS84 (FIRST, element)) && null == (object = parseVerticalDatum (FIRST, element, null, false)) && null == (object = parseTimeDatum (FIRST, element)) && null == (object = parseParametricDatum (FIRST, element)) && null == (object = parseEngineeringDatum (FIRST, element, false)) && null == (object = parseImageDatum (FIRST, element)) - && null == (object = parseOperation (FIRST, element)) + && null == (object = parseEllipsoid (FIRST, element)) + && null == (object = parsePrimeMeridian (FIRST, element, false, Units.DEGREE)) + && null == (object = parseAxis (FIRST, element, null, Units.METRE )) + && null == (object = parseToWGS84 (FIRST, element)) && null == (object = parseGeogTranslation (FIRST, element))) { throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS); @@ -1485,15 +1485,15 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo * * @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. + * @param datumType GeoAPI interface of the type of datum to create. * @return the {@code "Ensemble"} element as a {@link 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, - final Class<D> datumType, final PrimeMeridian meridian) throws ParseException + final PrimeMeridian meridian, final Class<D> datumType) throws ParseException { final Element ensemble = parent.pullElement(mode, WKTKeywords.Ensemble); if (ensemble == null) { @@ -1826,7 +1826,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo } } if (baseCRS == null) { // The most usual case. - ensemble = parseEnsemble(OPTIONAL, element, EngineeringDatum.class, null); + ensemble = parseEnsemble(OPTIONAL, element, null, EngineeringDatum.class); datum = parseEngineeringDatum(ensemble == null ? MANDATORY : OPTIONAL, element, isWKT1); } final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; @@ -2037,7 +2037,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo meridian = greenwich(); } final Temporal epoch = parseDynamic(element); - final DatumEnsemble<GeodeticDatum> ensemble = parseEnsemble(OPTIONAL, element, GeodeticDatum.class, meridian); + final DatumEnsemble<GeodeticDatum> ensemble = parseEnsemble(OPTIONAL, element, meridian, GeodeticDatum.class); 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); @@ -2112,7 +2112,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo } if (baseCRS == null) { // The most usual case. final Temporal epoch = parseDynamic(element); - ensemble = parseEnsemble(OPTIONAL, element, VerticalDatum.class, null); + ensemble = parseEnsemble(OPTIONAL, element, null, VerticalDatum.class); datum = parseVerticalDatum(ensemble == null ? MANDATORY : OPTIONAL, element, epoch, isWKT1); } final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; @@ -2201,7 +2201,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo } } if (baseCRS == null) { // The most usual case. - ensemble = parseEnsemble(OPTIONAL, element, TemporalDatum.class, null); + ensemble = parseEnsemble(OPTIONAL, element, null, TemporalDatum.class); datum = parseTimeDatum(ensemble == null ? MANDATORY : OPTIONAL, element); } final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; @@ -2265,7 +2265,7 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo } } if (baseCRS == null) { // The most usual case. - ensemble = parseEnsemble(OPTIONAL, element, ParametricDatum.class, null); + ensemble = parseEnsemble(OPTIONAL, element, null, ParametricDatum.class); datum = parseParametricDatum(ensemble == null ? MANDATORY : OPTIONAL, element); } final IdentifiedObject datumOrEnsemble = (datum != null) ? datum : ensemble; @@ -2492,26 +2492,58 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo } /** - * Parses a {@code "CoordinateOperation"} element. + * Parses a {@code "CoordinateOperation"} or {@code "ConcatenatedOperation"} element. + * This method accepts nested concatenated operations, even if not valid according + * <abbr>ISO</abbr> standards. Those nested operations will be flattened. * * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. * @param parent the parent element. - * @return the {@code "CoordinateOperation"} element as a {@link CoordinateOperation} object. - * @throws ParseException if the {@code "CoordinateOperation"} element cannot be parsed. + * @return the {@code "CoordinateOperation"} or {@code "ConcatenatedOperation"} element. + * @throws ParseException if the element cannot be parsed. */ private CoordinateOperation parseOperation(final int mode, final Element parent) throws ParseException { - final Element element = parent.pullElement(mode, WKTKeywords.CoordinateOperation); + final Element element = parent.pullElement(mode, WKTKeywords.CoordinateOperation, WKTKeywords.ConcatenatedOperation); if (element == null) { return null; } - final String name = element.pullString("name"); - final String version = pullElementAsString(element, WKTKeywords.Version); - final CoordinateReferenceSystem sourceCRS = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.SourceCRS); - final CoordinateReferenceSystem targetCRS = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.TargetCRS); - final CoordinateReferenceSystem interpolationCRS = parseCoordinateReferenceSystem(element, OPTIONAL, WKTKeywords.InterpolationCRS); - final OperationMethod method = parseMethod(element, WKTKeywords.Method); - final double accuracy = pullElementAsDouble(element, WKTKeywords.OperationAccuracy, OPTIONAL); - final Map<String,Object> properties = parseParametersAndClose(element, name, method); + final boolean concat = element.getKeywordIndex() != 0; + final String name = element.pullString("name"); + final String version = pullElementAsString(element, WKTKeywords.Version); + final double accuracy = pullElementAsDouble(element, WKTKeywords.OperationAccuracy, OPTIONAL); + final CoordinateReferenceSystem sourceCRS = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.SourceCRS); + final CoordinateReferenceSystem targetCRS = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.TargetCRS); + final DefaultCoordinateOperationFactory df = getOperationFactory(); + try { + if (concat) { + final var steps = new ArrayList<CoordinateOperation>(); + Element step; + while ((step = element.pullElement(steps.isEmpty() ? MANDATORY : OPTIONAL, WKTKeywords.Step)) != null) { + steps.add(parseOperation(MANDATORY, step)); + step.close(ignoredElements); + } + Map<String,Object> properties = parseMetadataAndClose(element, name, null); + addOperationMetadata(properties, version, accuracy); + return df.createConcatenatedOperation(properties, sourceCRS, targetCRS, steps.toArray(CoordinateOperation[]::new)); + } else { + CoordinateReferenceSystem interpolationCRS = parseCoordinateReferenceSystem(element, OPTIONAL, WKTKeywords.InterpolationCRS); + OperationMethod method = parseMethod(element, WKTKeywords.Method); + Map<String,Object> properties = parseParametersAndClose(element, name, method); + addOperationMetadata(properties, version, accuracy); + return df.createSingleOperation(properties, sourceCRS, targetCRS, interpolationCRS, method, null); + } + } catch (FactoryException e) { + throw element.parseFailed(e); + } + } + + /** + * Stores in the given map some additional metadata that are specific to coordinate operations. + * + * @param properties where to add the metadata. + * @param version the operation version, or {@code null} if none. + * @param accuracy the operation accuracy, or {@code null} if none. + */ + private static void addOperationMetadata(final Map<String,Object> properties, final String version, final double accuracy) { if (version != null) { properties.put(CoordinateOperation.OPERATION_VERSION_KEY, version); } @@ -2519,12 +2551,6 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY, PositionalAccuracyConstant.transformation(accuracy)); } - try { - final DefaultCoordinateOperationFactory df = getOperationFactory(); - return df.createSingleOperation(properties, sourceCRS, targetCRS, interpolationCRS, method, null); - } catch (FactoryException e) { - throw element.parseFailed(e); - } } /** diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java index 369e9182aa..d168be5067 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java @@ -975,8 +975,7 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 * because the WKT 2 specification does not define pass-through operations. * This choice may change in any future SIS version. */ - final FormattableObject enclosing = formatter.getEnclosingElement(1); - final boolean isSubOperation = (enclosing instanceof PassThroughOperation); + final boolean isSubOperation = (formatter.getEnclosingElement(1) instanceof PassThroughOperation); boolean isGeogTran = false; if (!isSubOperation) { isGeogTran = isWKT1 && (sourceCRS instanceof GeographicCRS) && (targetCRS instanceof GeographicCRS); @@ -1042,17 +1041,8 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 return WKTKeywords.GeogTran; } } - /* - * If the coordinate operation is a step in a chain of operations, returns "step". - * Otherwise, if formatting a top-level single operation, add the interpolation CRS. - */ - if (enclosing instanceof ConcatenatedOperation) { - return WKTKeywords.Step; - } - if (isSubOperation) { - if (!(this instanceof ConcatenatedOperation)) { - append(formatter, getInterpolationCRS().orElse(null), WKTKeywords.InterpolationCRS); - } + if (!(isSubOperation || this instanceof ConcatenatedOperation)) { + append(formatter, getInterpolationCRS().orElse(null), WKTKeywords.InterpolationCRS); WKTUtilities.appendElementIfPositive(WKTKeywords.OperationAccuracy, getLinearAccuracy(), formatter); } return WKTKeywords.CoordinateOperation; diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java index 00aec82220..57917b27c6 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java @@ -38,6 +38,7 @@ import org.apache.sis.referencing.datum.DatumOrEnsemble; import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; import org.apache.sis.referencing.privy.WKTKeywords; +import org.apache.sis.referencing.privy.WKTUtilities; import org.apache.sis.referencing.privy.CoordinateOperations; import org.apache.sis.referencing.internal.PositionalAccuracyConstant; import org.apache.sis.referencing.internal.Resources; @@ -49,6 +50,7 @@ import org.apache.sis.util.privy.UnmodifiableArrayList; import org.apache.sis.util.privy.Constants; import org.apache.sis.util.resources.Errors; import org.apache.sis.io.wkt.Convention; +import org.apache.sis.io.wkt.FormattableObject; import org.apache.sis.io.wkt.Formatter; @@ -59,6 +61,9 @@ import org.apache.sis.io.wkt.Formatter; * first step and the target coordinate reference system of the last step are the source and target coordinate * reference system associated with the concatenated operation. * + * <p>Above requirements are relaxed when the source and target <abbr>CRS</abbr> of a step are swapped. + * In such case, this step will actually be implemented by the inverse operation.</p> + * * @author Martin Desruisseaux (IRD, Geomatys) */ @XmlType(name = "ConcatenatedOperationType") @@ -136,18 +141,26 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp * or for overriding the automatic concatenation. * * @param properties the properties to be given to the identified object. + * @param sourceCRS the source <abbr>CRS</abbr>, or {@code null} for the source of the first step. + * @param targetCRS the target <abbr>CRS</abbr>, or {@code null} for the target of the last effective step. * @param operations the sequence of operations. Shall contain at least two operations. * @param mtFactory the math transform factory to use for math transforms concatenation. * @throws FactoryException if this constructor or the factory cannot concatenate the operation steps. */ - public DefaultConcatenatedOperation(final Map<String,?> properties, final CoordinateOperation[] operations, - final MathTransformFactory mtFactory) throws FactoryException + public DefaultConcatenatedOperation(final Map<String,?> properties, + final CoordinateReferenceSystem sourceCRS, + final CoordinateReferenceSystem targetCRS, + final CoordinateOperation[] operations, + final MathTransformFactory mtFactory) + throws FactoryException { super(properties); if (operations.length < 2) { throw new InvalidGeodeticParameterException(Errors.forProperties(properties).getString( Errors.Keys.TooFewOccurrences_2, 2, CoordinateOperation.class)); } + this.sourceCRS = sourceCRS; + this.targetCRS = targetCRS; transform = Containers.property(properties, TRANSFORM_KEY, MathTransform.class); initialize(properties, operations, (transform == null) ? mtFactory : null); checkDimensions(properties); @@ -155,8 +168,8 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp /** * Initializes the {@link #sourceCRS}, {@link #targetCRS} and {@link #operations} fields. - * If the source or target CRS is already non-null (which may happen on JAXB unmarshalling), - * leaves that CRS unchanged. + * If the source and target <abbr>CRS</abbr> are already non-null, leaves them unchanged + * but verifies that they are consistent with the first and last steps. * * @param properties the properties specified at construction time, or {@code null} if unknown. * @param operations the operations to concatenate. @@ -317,7 +330,7 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp } } } - if (!(mtFactory instanceof DefaultMathTransformFactory)) { + if (mtFactory != null) { verifyStepChaining(properties, operations.length, target, targetCRS, null); // Else verification will be done by the caller. } @@ -461,8 +474,15 @@ final class DefaultConcatenatedOperation extends AbstractCoordinateOperation imp super.formatTo(formatter); for (final CoordinateOperation component : operations) { formatter.newLine(); - formatter.append(castOrCopy(component)); + formatter.append(new FormattableObject() { + @Override protected String formatTo(Formatter formatter) { + formatter.newLine(); + formatter.appendFormattable(component, AbstractCoordinateOperation::castOrCopy); + return WKTKeywords.Step; + } + }); } + WKTUtilities.appendElementIfPositive(WKTKeywords.OperationAccuracy, getLinearAccuracy(), formatter); if (!formatter.getConvention().supports(Convention.WKT2_2019)) { formatter.setInvalidWKT(this, null); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java index 8e24b9dc6e..6c67f525e5 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java @@ -559,6 +559,26 @@ next: for (SingleCRS component : CRS.getSingleComponents(targetCRS)) { return pool.unique(op); } + /** + * Creates an ordered sequence of two or more single coordinate operations. + * + * @deprecated Replaced by {@linkplain #createConcatenatedOperation(Map, CoordinateReferenceSystem, + * CoordinateReferenceSystem, CoordinateOperation...) a method with explicit CRS arguments} because + * of potential <abbr>CRS</abbr> swapping. + * + * @param properties the properties to be given to the identified object. + * @param operations the sequence of operations. Shall contain at least two operations. + * @return the concatenated operation created from the given arguments. + * @throws FactoryException if the object creation failed. + */ + @Override + @Deprecated(since="1.5", forRemoval=true) + public CoordinateOperation createConcatenatedOperation(final Map<String,?> properties, + final CoordinateOperation... operations) throws FactoryException + { + return createConcatenatedOperation(properties, null, null, operations); + } + /** * Creates an ordered sequence of two or more single coordinate operations. * The sequence of operations is constrained by the requirement that the source coordinate reference system @@ -566,6 +586,12 @@ next: for (SingleCRS component : CRS.getSingleComponents(targetCRS)) { * The source coordinate reference system of the first step and the target coordinate reference system of the * last step are the source and target coordinate reference system associated with the concatenated operation. * + * <p>As an exception to the above-cited constraint, a step can swap its source and target <abbr>CRS</abbr>. + * In such case, the effectively executed operation will be the inverse of that step. The {@code sourceCRS} + * and {@code targetCRS} arguments of this method are needed for detecting whether such swapping occurred + * in the first step or in the last step. Those optional arguments can be {@code null} if the caller did + * not swapped any <abbr>CRS</abbr>.</p> + * * <p>The properties given in argument follow the same rules as for any other * {@linkplain AbstractCoordinateOperation#AbstractCoordinateOperation(Map, CoordinateReferenceSystem, * CoordinateReferenceSystem, CoordinateReferenceSystem, MathTransform) coordinate operation} constructor. @@ -589,12 +615,18 @@ next: for (SingleCRS component : CRS.getSingleComponents(targetCRS)) { * </table> * * @param properties the properties to be given to the identified object. + * @param sourceCRS the source <abbr>CRS</abbr>, or {@code null} for the source of the first step. + * @param targetCRS the target <abbr>CRS</abbr>, or {@code null} for the target of the last effective step. * @param operations the sequence of operations. Shall contain at least two operations. * @return the concatenated operation created from the given arguments. * @throws FactoryException if the object creation failed. + * + * @since 1.5 */ - @Override - public CoordinateOperation createConcatenatedOperation(final Map<String,?> properties, + public CoordinateOperation createConcatenatedOperation( + final Map<String,?> properties, + final CoordinateReferenceSystem sourceCRS, + final CoordinateReferenceSystem targetCRS, final CoordinateOperation... operations) throws FactoryException { /* @@ -604,10 +636,10 @@ next: for (SingleCRS component : CRS.getSingleComponents(targetCRS)) { * code, in which case we do not want to modify any other metadata in order to stay compliant * with EPSG definition). */ - if (operations != null && operations.length == 1) { + if (operations.length == 1 && sourceCRS == null && targetCRS == null) { return operations[0]; } - final var op = new DefaultConcatenatedOperation(properties, operations, getMathTransformFactory()); + final var op = new DefaultConcatenatedOperation(properties, sourceCRS, targetCRS, operations, getMathTransformFactory()); /* * Verifies again the number of single operations. We may have a singleton if some operations * were omitted because their associated math transform were identity. This happen for example diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java index a8ac95a57c..00525ad83a 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultConcatenatedOperationTest.java @@ -67,6 +67,7 @@ public final class DefaultConcatenatedOperationTest extends TestCase { /** * Creates a “Tokyo to JGD2000” transformation. + * This is defined by {@code EPSG:15483}, but this test does not reproduce all metadata. * * @see DefaultTransformationTest#createGeocentricTranslation() */ @@ -92,6 +93,7 @@ public final class DefaultConcatenatedOperationTest extends TestCase { return new DefaultConcatenatedOperation( Map.of(DefaultConversion.NAME_KEY, "Tokyo to JGD2000"), + null, null, new AbstractSingleOperation[] {before, op, after}, mtFactory); } @@ -121,63 +123,66 @@ public final class DefaultConcatenatedOperationTest extends TestCase { " Axis[“Longitude (L)”, east, Unit[“degree”, 0.017453292519943295]],\n" + " Axis[“Latitude (B)”, north, Unit[“degree”, 0.017453292519943295]],\n" + " Axis[“Ellipsoidal height (h)”, up, Unit[“metre”, 1]]]],\n" + - " Step[“Geographic to geocentric”,\n" + - " SourceCRS[GeographicCRS[“Tokyo”,\n" + - " Datum[“Tokyo 1918”,\n" + - " Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128]],\n" + - " CS[ellipsoidal, 3],\n" + - " Axis[“Longitude (L)”, east, Unit[“degree”, 0.017453292519943295]],\n" + - " Axis[“Latitude (B)”, north, Unit[“degree”, 0.017453292519943295]],\n" + - " Axis[“Ellipsoidal height (h)”, up, Unit[“metre”, 1]]]],\n" + - " TargetCRS[GeodeticCRS[“Tokyo 1918”,\n" + - " Datum[“Tokyo 1918”,\n" + - " Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128]],\n" + - " CS[Cartesian, 3],\n" + - " Axis[“(X)”, geocentricX],\n" + - " Axis[“(Y)”, geocentricY],\n" + - " Axis[“(Z)”, geocentricZ],\n" + - " Unit[“metre”, 1]]],\n" + - " Method[“Geographic/geocentric conversions”]],\n" + // Omit non-EPSG parameters for EPSG method. - " Step[“Tokyo to JGD2000 (GSI)”, Version[“GSI-Jpn”],\n" + - " SourceCRS[GeodeticCRS[“Tokyo 1918”,\n" + - " Datum[“Tokyo 1918”,\n" + - " Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128]],\n" + - " CS[Cartesian, 3],\n" + - " Axis[“(X)”, geocentricX],\n" + - " Axis[“(Y)”, geocentricY],\n" + - " Axis[“(Z)”, geocentricZ],\n" + - " Unit[“metre”, 1]]],\n" + - " TargetCRS[GeodeticCRS[“JGD2000”,\n" + - " Datum[“Japanese Geodetic Datum 2000”,\n" + - " Ellipsoid[“GRS 1980”, 6378137.0, 298.257222101]],\n" + - " CS[Cartesian, 3],\n" + - " Axis[“(X)”, geocentricX],\n" + - " Axis[“(Y)”, geocentricY],\n" + - " Axis[“(Z)”, geocentricZ],\n" + - " Unit[“metre”, 1]]],\n" + - " Method[“Geocentric translations”],\n" + - " Parameter[“X-axis translation”, -146.414],\n" + - " Parameter[“Y-axis translation”, 507.337],\n" + - " Parameter[“Z-axis translation”, 680.507]],\n" + - " Step[“Geocentric to geographic”,\n" + - " SourceCRS[GeodeticCRS[“JGD2000”,\n" + - " Datum[“Japanese Geodetic Datum 2000”,\n" + - " Ellipsoid[“GRS 1980”, 6378137.0, 298.257222101]],\n" + - " CS[Cartesian, 3],\n" + - " Axis[“(X)”, geocentricX],\n" + - " Axis[“(Y)”, geocentricY],\n" + - " Axis[“(Z)”, geocentricZ],\n" + - " Unit[“metre”, 1]]],\n" + - " TargetCRS[GeographicCRS[“JGD2000”,\n" + - " Datum[“Japanese Geodetic Datum 2000”,\n" + - " Ellipsoid[“GRS 1980”, 6378137.0, 298.257222101]],\n" + - " CS[ellipsoidal, 3],\n" + - " Axis[“Longitude (L)”, east, Unit[“degree”, 0.017453292519943295]],\n" + - " Axis[“Latitude (B)”, north, Unit[“degree”, 0.017453292519943295]],\n" + - " Axis[“Ellipsoidal height (h)”, up, Unit[“metre”, 1]]]],\n" + - " Method[“Geographic/geocentric conversions”],\n" + - " Parameter[“semi_major”, 6378137.0, Unit[“metre”, 1]],\n" + - " Parameter[“semi_minor”, 6356752.314140356, Unit[“metre”, 1]]]]", op); + " Step[\n" + + " CoordinateOperation[“Geographic to geocentric”,\n" + + " SourceCRS[GeographicCRS[“Tokyo”,\n" + + " Datum[“Tokyo 1918”,\n" + + " Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128]],\n" + + " CS[ellipsoidal, 3],\n" + + " Axis[“Longitude (L)”, east, Unit[“degree”, 0.017453292519943295]],\n" + + " Axis[“Latitude (B)”, north, Unit[“degree”, 0.017453292519943295]],\n" + + " Axis[“Ellipsoidal height (h)”, up, Unit[“metre”, 1]]]],\n" + + " TargetCRS[GeodeticCRS[“Tokyo 1918”,\n" + + " Datum[“Tokyo 1918”,\n" + + " Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128]],\n" + + " CS[Cartesian, 3],\n" + + " Axis[“(X)”, geocentricX],\n" + + " Axis[“(Y)”, geocentricY],\n" + + " Axis[“(Z)”, geocentricZ],\n" + + " Unit[“metre”, 1]]],\n" + + " Method[“Geographic/geocentric conversions”]]],\n" + // Omit non-EPSG parameters for EPSG method. + " Step[\n" + + " CoordinateOperation[“Tokyo to JGD2000 (GSI)”, Version[“GSI-Jpn”],\n" + + " SourceCRS[GeodeticCRS[“Tokyo 1918”,\n" + + " Datum[“Tokyo 1918”,\n" + + " Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128]],\n" + + " CS[Cartesian, 3],\n" + + " Axis[“(X)”, geocentricX],\n" + + " Axis[“(Y)”, geocentricY],\n" + + " Axis[“(Z)”, geocentricZ],\n" + + " Unit[“metre”, 1]]],\n" + + " TargetCRS[GeodeticCRS[“JGD2000”,\n" + + " Datum[“Japanese Geodetic Datum 2000”,\n" + + " Ellipsoid[“GRS 1980”, 6378137.0, 298.257222101]],\n" + + " CS[Cartesian, 3],\n" + + " Axis[“(X)”, geocentricX],\n" + + " Axis[“(Y)”, geocentricY],\n" + + " Axis[“(Z)”, geocentricZ],\n" + + " Unit[“metre”, 1]]],\n" + + " Method[“Geocentric translations”],\n" + + " Parameter[“X-axis translation”, -146.414],\n" + + " Parameter[“Y-axis translation”, 507.337],\n" + + " Parameter[“Z-axis translation”, 680.507]]],\n" + + " Step[\n" + + " CoordinateOperation[“Geocentric to geographic”,\n" + + " SourceCRS[GeodeticCRS[“JGD2000”,\n" + + " Datum[“Japanese Geodetic Datum 2000”,\n" + + " Ellipsoid[“GRS 1980”, 6378137.0, 298.257222101]],\n" + + " CS[Cartesian, 3],\n" + + " Axis[“(X)”, geocentricX],\n" + + " Axis[“(Y)”, geocentricY],\n" + + " Axis[“(Z)”, geocentricZ],\n" + + " Unit[“metre”, 1]]],\n" + + " TargetCRS[GeographicCRS[“JGD2000”,\n" + + " Datum[“Japanese Geodetic Datum 2000”,\n" + + " Ellipsoid[“GRS 1980”, 6378137.0, 298.257222101]],\n" + + " CS[ellipsoidal, 3],\n" + + " Axis[“Longitude (L)”, east, Unit[“degree”, 0.017453292519943295]],\n" + + " Axis[“Latitude (B)”, north, Unit[“degree”, 0.017453292519943295]],\n" + + " Axis[“Ellipsoidal height (h)”, up, Unit[“metre”, 1]]]],\n" + + " Method[“Geographic/geocentric conversions”],\n" + + " Parameter[“semi_major”, 6378137.0, Unit[“metre”, 1]],\n" + + " Parameter[“semi_minor”, 6356752.314140356, Unit[“metre”, 1]]]]]", op); } /**
