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 7ace1d83d11f8351d319db7b81af1cfa5cd8623b
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Jan 30 16:10:49 2024 +0100

    Add an option for requesting the inverse of an operation on the 
command-line.
---
 .../main/org/apache/sis/console/Option.java        |  6 ++
 .../main/org/apache/sis/console/Options.properties |  1 +
 .../org/apache/sis/console/Options_fr.properties   |  1 +
 .../main/org/apache/sis/console/SIS.java           | 10 +++
 .../org/apache/sis/console/TransformCommand.java   | 87 +++++++++++++++++-----
 5 files changed, 88 insertions(+), 17 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
index 3d42dd5a7c..030cbbfd11 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Option.java
@@ -43,6 +43,12 @@ enum Option {
      */
     OPERATION(true),
 
+    /**
+     * Use the inverse of the coordinate operation. The transform will be 
inverted <em>after</em> all
+     * other options ({@link #OPERATION}, {@link #SOURCE_CRS} and {@link 
#TARGET_CRS}) have been applied.
+     */
+    INVERSE(false),
+
     /**
      * Relative path to an auxiliary metadata file.
      */
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
index b0a7d90e26..64d1ed144e 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options.properties
@@ -3,6 +3,7 @@
 sourceCRS=The Coordinate Reference System of input data.
 targetCRS=The Coordinate Reference System of output data.
 operation=The Coordinate Operation to apply on data (alternative to source and 
target CRS).
+inverse=Use the inverse of the coordinate operation.
 metadata=Relative path to an auxiliary metadata file.
 output=The output file.
 format=The output format. Examples: xml, wkt, wkt1 or text.
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
index efffd7a511..e4cb67fb93 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/Options_fr.properties
@@ -3,6 +3,7 @@
 sourceCRS=Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es source.
 targetCRS=Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es 
destination.
 operation=L\u2019op\u00e9ration \u00e0 appliquer sur les coordonn\u00e9es 
(alternative aux CRS source et destination).
+inverse=Utiliser l'inverse de l\u2019op\u00e9ration sur les coordonn\u00e9es.
 metadata=Chemin relatif vers un fichier auxiliaire de m\u00e9ta-donn\u00e9es.
 output=Le fichier de sortie.
 format=Le format de sortie. Exemples: xml, wkt, wkt1 ou text.
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
index 26fb629a57..4e01778691 100644
--- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
+++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
@@ -655,6 +655,16 @@ public final class SIS extends Static {
             return set(Option.OPERATION, value);
         }
 
+        /**
+         * Use the inverse of the coordinate operation. The transform will be 
inverted <em>after</em> all
+         * other options ({@code operation}, {@code sourceCRS} and {@code 
targetCRS}) have been applied.
+         *
+         * @return a new builder or {@code this}, for method call chaining.
+         */
+        public Transform inverse() {
+            return set(Option.INVERSE, null);
+        }
+
         /**
          * Sets the locale to use for the command output.
          *
diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
index 3e89a6b245..c4ec39364c 100644
--- 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
+++ 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/TransformCommand.java
@@ -21,13 +21,13 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.EnumSet;
 import java.util.Locale;
+import java.util.logging.Logger;
 import java.io.IOException;
 import java.io.LineNumberReader;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.BufferedReader;
 import java.text.NumberFormat;
-import static java.util.logging.Logger.getLogger;
 import javax.measure.Unit;
 import javax.measure.IncommensurableException;
 import org.opengis.metadata.Metadata;
@@ -50,6 +50,7 @@ import 
org.opengis.referencing.operation.ConcatenatedOperation;
 import org.opengis.referencing.operation.PassThroughOperation;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.CRS;
@@ -104,11 +105,33 @@ import org.opengis.referencing.ObjectDomain;
  * @author  Martin Desruisseaux (Geomatys)
  */
 final class TransformCommand extends FormattedOutputCommand {
+    /**
+     * The logger for command-line tools.
+     * Currently defined in this class because it is the only one to use 
directly this logger.
+     */
+    private static final Logger LOGGER = Logger.getLogger(Modules.CONSOLE);
+
     /**
      * The coordinate operation from the given source CRS to target CRS.
+     * This is always the forward operation. The inverse needs to be used
+     * if the {@link #inverse} flag is {@code true}.
      */
     private CoordinateOperation operation;
 
+    /**
+     * The user-supplied or inferred source and target CRS. They are not 
necessarily the source and target CRS of
+     * the transform to apply on coordinate tuples, because they need to be 
swapped if {@link #inverse} is true.
+     *
+     * @see #getEffectiveCRS(boolean)
+     */
+    private CoordinateReferenceSystem sourceCRS, targetCRS;
+
+    /**
+     * Whether to use the inverse of the transform given by {@link #operation}.
+     * The transform will be inverted after all other options have been 
applied.
+     */
+    private final boolean inverse;
+
     /**
      * The transformation from source CRS to the domain of validity CRS, or 
{@code null} if none.
      */
@@ -158,7 +181,7 @@ final class TransformCommand extends FormattedOutputCommand 
{
      * Returns valid options for the {@code "transform"} commands.
      */
     private static EnumSet<Option> options() {
-        return EnumSet.of(Option.SOURCE_CRS, Option.TARGET_CRS, 
Option.OPERATION, Option.VERBOSE,
+        return EnumSet.of(Option.SOURCE_CRS, Option.TARGET_CRS, 
Option.OPERATION, Option.INVERSE, Option.VERBOSE,
                 Option.LOCALE, Option.TIMEZONE, Option.ENCODING, 
Option.COLORS, Option.HELP, Option.DEBUG);
     }
 
@@ -168,6 +191,7 @@ final class TransformCommand extends FormattedOutputCommand 
{
     TransformCommand(final int commandIndex, final Object[] args) throws 
InvalidOptionException {
         super(commandIndex, args, options(), OutputFormat.WKT, 
OutputFormat.TEXT);
         resources = Vocabulary.forLocale(locale);
+        inverse = options.containsKey(Option.INVERSE);
     }
 
     /**
@@ -249,8 +273,8 @@ final class TransformCommand extends FormattedOutputCommand 
{
     @Override
     public int run() throws Exception {
         fetchOperation(options.get(Option.OPERATION));
-        CoordinateReferenceSystem sourceCRS = fetchCRS(Option.SOURCE_CRS, 
operation == null);
-        CoordinateReferenceSystem targetCRS = fetchCRS(Option.TARGET_CRS, 
operation == null);
+        sourceCRS = fetchCRS(Option.SOURCE_CRS, operation == null);
+        targetCRS = fetchCRS(Option.TARGET_CRS, operation == null);
         if (sourceCRS == null) sourceCRS = operation.getSourceCRS();    // May 
still be null.
         if (targetCRS == null) targetCRS = operation.getTargetCRS();
         /*
@@ -274,9 +298,10 @@ final class TransformCommand extends 
FormattedOutputCommand {
                 }
             }
             try {
-                final GeographicCRS domainOfValidityCRS = 
ReferencingUtilities.toNormalizedGeographicCRS(sourceCRS, false, false);
+                final CoordinateReferenceSystem crs = getEffectiveCRS(false);
+                final GeographicCRS domainOfValidityCRS = 
ReferencingUtilities.toNormalizedGeographicCRS(crs, false, false);
                 if (domainOfValidityCRS != null) {
-                    toDomainOfValidity = CRS.findOperation(sourceCRS, 
domainOfValidityCRS, null).getMathTransform();
+                    toDomainOfValidity = CRS.findOperation(crs, 
domainOfValidityCRS, null).getMathTransform();
                     areaOfInterest = computeAreaOfInterest(points);
                 }
             } catch (FactoryException e) {
@@ -312,14 +337,22 @@ final class TransformCommand extends 
FormattedOutputCommand {
                 operation = factory.createConcatenatedOperation(properties, 
steps.toArray(CoordinateOperation[]::new));
             }
         }
+        /*
+         * At this point, the coordinate operation has been fully constructed. 
Replace the source and target CRS
+         * by the ones specified in the operation because they may have more 
accurate metadata (name, identifier)
+         * if they were fetched from the EPSG database.
+         */
+        CoordinateReferenceSystem crs;
+        if ((crs = operation.getSourceCRS()) != null) sourceCRS = crs;
+        if ((crs = operation.getTargetCRS()) != null) targetCRS = crs;
         /*
          * Prints the header: source CRS, target CRS, operation steps and 
positional accuracy.
          */
         outHeader = new TableAppender(new LineAppender(out), " ");
         outHeader.setMultiLinesCells(true);
-        printHeader(Vocabulary.Keys.Source);      
printNameAndIdentifier(operation.getSourceCRS(), false);
-        printHeader(Vocabulary.Keys.Destination); 
printNameAndIdentifier(operation.getTargetCRS(), false);
-        printHeader(Vocabulary.Keys.Operations);  printOperations (operation, 
false);
+        printHeader(Vocabulary.Keys.Source);      
printNameAndIdentifier(getEffectiveCRS(false), false);
+        printHeader(Vocabulary.Keys.Destination); 
printNameAndIdentifier(getEffectiveCRS(true),  false);
+        printHeader(Vocabulary.Keys.Operations);  printOperations(operation, 
false);
         outHeader.nextLine();
         printDomainOfValidity(operation.getDomains());
         printAccuracy(CRS.getLinearAccuracy(operation));
@@ -336,9 +369,10 @@ final class TransformCommand extends 
FormattedOutputCommand {
             coordinateWidth  = 15;          // Must be set before 
computeNumFractionDigits(…).
             coordinateFormat = NumberFormat.getInstance(Locale.US);
             coordinateFormat.setGroupingUsed(false);
-            
computeNumFractionDigits(operation.getTargetCRS().getCoordinateSystem());
+            final CoordinateSystem cs = 
getEffectiveCRS(true).getCoordinateSystem();
+            computeNumFractionDigits(cs);
             out.println();
-            printAxes(operation.getTargetCRS().getCoordinateSystem());
+            printAxes(cs);
             out.println();
             transform(points);
             if (errorMessage != null) {
@@ -491,11 +525,11 @@ final class TransformCommand extends 
FormattedOutputCommand {
      * Prints the coordinate operation or math transform in Well Known Text 
format.
      * This information is printed only if the {@code --verbose} option was 
specified.
      */
-    private void printDetails() throws IOException {
+    private void printDetails() throws IOException, 
NoninvertibleTransformException {
         final WKTFormat f = new WKTFormat(locale, timezone);
         if (colors) f.setColors(Colors.DEFAULT);
         f.setConvention(convention);
-        CharSequence[] lines = CharSequences.splitOnEOL(f.format(debug ? 
operation.getMathTransform() : operation));
+        CharSequence[] lines = CharSequences.splitOnEOL(f.format(debug ? 
getMathTransform() : operation));
         for (int i=0; i<lines.length; i++) {
             if (i == 0) {
                 printHeader(Vocabulary.Keys.Details);
@@ -656,12 +690,31 @@ final class TransformCommand extends 
FormattedOutputCommand {
         return null;
     }
 
+    /**
+     * Returns the source or target CRS, taking in account whether the inverse 
transform is requested.
+     *
+     * @param  target  {@code false} for the source CRS, or {@code true} for 
the target CRS.
+     * @return the source or target CRS.
+     */
+    private CoordinateReferenceSystem getEffectiveCRS(final boolean target) {
+        return (target ^ inverse) ? targetCRS : sourceCRS;
+    }
+
+    /**
+     * {@return the math transform of the operation, inverted if requested by 
user}.
+     */
+    private MathTransform getMathTransform() throws 
NoninvertibleTransformException {
+        MathTransform mt = operation.getMathTransform();
+        if (inverse) mt = mt.inverse();
+        return mt;
+    }
+
     /**
      * Transforms the given coordinates.
      */
     private void transform(final List<double[]> points) throws 
TransformException {
-        final int dimension    = 
operation.getSourceCRS().getCoordinateSystem().getDimension();
-        final MathTransform mt = operation.getMathTransform();
+        final MathTransform mt = getMathTransform();
+        final int dimension    = mt.getSourceDimensions();
         final double[] result  = new double[mt.getTargetDimensions()];
         final double[] domainCoordinate;
         final DirectPositionView positionInDomain;
@@ -679,7 +732,7 @@ final class TransformCommand extends FormattedOutputCommand 
{
         for (final double[] coordinates : points) {
             if (coordinates.length != dimension) {
                 throw new 
MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimensionForCRS_3,
-                            operation.getSourceCRS().getName().getCode(), 
dimension, coordinates.length));
+                            getEffectiveCRS(false).getName().getCode(), 
dimension, coordinates.length));
             }
             /*
              * At this point we got the coordinates and they have the expected 
number of dimensions.
@@ -733,6 +786,6 @@ final class TransformCommand extends FormattedOutputCommand 
{
      * considered as an indication that the point is outside the domain of 
validity.
      */
     private static void warning(final Exception e) {
-        Logging.recoverableException(getLogger(Modules.CONSOLE), 
TransformCommand.class, "run", e);
+        Logging.recoverableException(LOGGER, TransformCommand.class, "run", e);
     }
 }

Reply via email to