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 7e0a63e4511da34d79bf95baffca1282c81db417
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Sep 23 11:34:28 2022 +0200

    Add convenience method for deriving a projection from an existing one,
    and for resampling a grid coverage by specifying only the new CRS.
---
 .../sis/coverage/grid/GridCoverageProcessor.java   |  24 +++++
 .../referencing/GeodeticObjectBuilder.java         | 102 +++++++++++++++++----
 .../factory/CommonAuthorityFactory.java            |   2 +-
 .../referencing/GeodeticObjectBuilderTest.java     |  57 ++++++++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 .../apache/sis/storage/landsat/MetadataReader.java |   2 +-
 6 files changed, 167 insertions(+), 21 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index db6cc2652b..9733d1fc19 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -27,6 +27,8 @@ import java.awt.image.ColorModel;
 import java.awt.image.RenderedImage;
 import javax.measure.Quantity;
 import org.opengis.util.FactoryException;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.coverage.RegionOfInterest;
@@ -480,6 +482,28 @@ public class GridCoverageProcessor implements Cloneable {
         return resampled.forConvertedValues(isConverted);
     }
 
+    /**
+     * Creates a new coverage with a different coordinate reference system.
+     * The grid extent and "grid to CRS" transform are determined automatically
+     * with default values preserving the resolution of source coverage at its
+     * {@linkplain GridExtent#getPointOfInterest(PixelInCell) point of 
interest}.
+     *
+     * <p>See {@link #resample(GridCoverage, GridGeometry)} for more 
information
+     * about interpolation and allowed optimizations.</p>
+     *
+     * @param  source  the grid coverage to resample.
+     * @param  target  the desired coordinate reference system.
+     * @return a grid coverage with the given coordinate reference system.
+     * @throws IncompleteGridGeometryException if the source grid geometry is 
missing an information.
+     * @throws TransformException if some coordinates can not be transformed 
to the specified target.
+     *
+     * @since 1.3
+     */
+    public GridCoverage resample(final GridCoverage source, final 
CoordinateReferenceSystem target) throws TransformException {
+        ArgumentChecks.ensureNonNull("target", target);
+        return resample(source, new GridGeometry(null, 
PixelInCell.CELL_CENTER, null, target));
+    }
+
     /**
      * Invoked when an ignorable exception occurred.
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
index 62ef1b36ee..bbd3da6fd4 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
@@ -20,12 +20,15 @@ import java.util.Map;
 import java.util.Date;
 import java.util.Collections;
 import java.util.Locale;
+import java.util.function.BiConsumer;
 import javax.measure.Unit;
 import javax.measure.quantity.Time;
 import javax.measure.quantity.Length;
 import org.opengis.util.GenericName;
 import org.opengis.util.FactoryException;
+import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.parameter.InvalidParameterValueException;
 import org.opengis.referencing.IdentifiedObject;
@@ -55,11 +58,15 @@ import org.apache.sis.measure.Latitude;
 import org.apache.sis.referencing.Builder;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.parameter.Parameters;
 
 
 /**
  * Helper methods for building Coordinate Reference Systems and related 
objects.
  *
+ * In current version, each builder instance should be used for creating only 
one CRS.
+ * Reusing the same builder for creating many CRS has unspecified behavior.
+ *
  * <p>For now, this class is defined in the internal package because this API 
needs more experimentation.
  * However this class may move in a public package later if we feel confident 
that its API is mature enough.</p>
  *
@@ -105,30 +112,20 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
     private final Locale locale;
 
     /**
-     * Creates a new builder.
+     * Creates a new builder with default locale and set of factories.
      */
     public GeodeticObjectBuilder() {
-        this(null);
-    }
-
-    /**
-     * Creates a new builder using the given locale for message in exceptions.
-     *
-     * @param  locale  the locale for error message in exceptions.
-     */
-    public GeodeticObjectBuilder(final Locale locale) {
-        factories = new ReferencingFactoryContainer();
-        this.locale = locale;
+        this(null, null);
     }
 
     /**
      * Creates a new builder using the given factories and locale.
      *
-     * @param  factories  the factories to use for geodetic objects creation.
-     * @param  locale     the locale for error message in exceptions.
+     * @param  factories  the factories to use for geodetic objects creation, 
or {@code null} for default.
+     * @param  locale     the locale for error message in exceptions, or 
{@code null} for default.
      */
     public GeodeticObjectBuilder(final ReferencingFactoryContainer factories, 
final Locale locale) {
-        this.factories = factories;
+        this.factories = (factories != null) ? factories : new 
ReferencingFactoryContainer();
         this.locale = locale;
     }
 
@@ -275,6 +272,73 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
         return this;
     }
 
+    /**
+     * Replaces the current operation method by a new one with parameter 
values derived form the old method.
+     * This method can be invoked for replacing a projection by another one 
with a similar set of parameters.
+     *
+     * <p>If non-null, the given {@code mapper} is used for copying parameter 
values from the old projection.
+     * The {@code accept(ParameterValue<?> source, Parameters target)} method 
is invoked where {@code source}
+     * is a parameter value of the old projection and {@code target} is the 
group of parameters where to set
+     * the values for new projection. If {@code mapper} is null, then the 
default implementation is as below:</p>
+     *
+     * {@preformat java
+     *     
target.getOrCreate(source.getDescriptor()).setValue(source.getValue());
+     * }
+     *
+     * @param  newMethod  name of the new operation method, or {@code null} if 
no change.
+     * @param  mapper     mapper from old parameters to new parameters, or 
{@code null} for verbatim copy.
+     * @return {@code this}, for method calls chaining.
+     * @throws IllegalStateException if {@link #setConversionMethod(String)} 
has not been invoked before this method.
+     * @throws FactoryException if the operation method of the given name can 
not be obtained.
+     * @throws ClassCastException if a parameter value of the old projection 
is not an instance of {@link ParameterValue}
+     *         (this restriction may change in a future version).
+     */
+    public GeodeticObjectBuilder changeConversion(final String newMethod,
+            BiConsumer<ParameterValue<?>, Parameters> mapper) throws 
FactoryException
+    {
+        ensureConversionMethodSet();
+        if (mapper == null) {
+            mapper = GeodeticObjectBuilder::copyParameterValue;
+        }
+        final ParameterValueGroup source = parameters;
+        if (newMethod != null) {
+            method = null;
+            setConversionMethod(newMethod);
+        }
+        final Parameters target = Parameters.castOrWrap(parameters);
+        for (final GeneralParameterValue param : source.values()) {
+            mapper.accept((ParameterValue<?>) param, target);       // 
ClassCastException is part of current method contract.
+        }
+        return this;
+    }
+
+    /**
+     * The default {@code mapper} of {@code changeConversion(String, 
BiConsumer)}.
+     *
+     * @param  source  parameter value of the old projection.
+     * @param  target  group of parameters of the new projection where to copy 
source parameter value.
+     */
+    private static void copyParameterValue(final ParameterValue<?> source, 
final Parameters target) {
+        target.getOrCreate(source.getDescriptor()).setValue(source.getValue());
+    }
+
+    /**
+     * Sets the operation method, parameters, conversion name and datum for 
the same projection than the given CRS.
+     * Metadata such as domain of validity are inherited, except identifiers.
+     *
+     * @param  crs  the projected CRS from which to inherit the properties.
+     * @return {@code this}, for method call chaining.
+     */
+    public GeodeticObjectBuilder apply(final ProjectedCRS crs) {
+        final Conversion c = crs.getConversionFromBase();
+        conversionName = c.getName().getCode();
+        method         = c.getMethod();
+        parameters     = c.getParameterValues();
+        datum          = crs.getDatum();
+        properties.putAll(IdentifiedObjects.getProperties(crs, 
ProjectedCRS.IDENTIFIERS_KEY));
+        return this;
+    }
+
     /**
      * Sets the operation method, parameters and conversion name for a 
Transverse Mercator projection.
      * This convenience method delegates to the following methods:
@@ -311,8 +375,8 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
      *
      * @see CommonCRS#universal(double, double)
      */
-    public GeodeticObjectBuilder 
setTransverseMercator(TransverseMercator.Zoner zoner, double latitude, double 
longitude)
-            throws FactoryException
+    public GeodeticObjectBuilder 
applyTransverseMercator(TransverseMercator.Zoner zoner,
+            double latitude, double longitude) throws FactoryException
     {
         ArgumentChecks.ensureBetween("latitude",   Latitude.MIN_VALUE,     
Latitude.MAX_VALUE,     latitude);
         ArgumentChecks.ensureBetween("longitude", -Formulas.LONGITUDE_MAX, 
Formulas.LONGITUDE_MAX, longitude);
@@ -347,7 +411,7 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
      * @throws FactoryException if the operation method for the Polar 
Stereographic (variant A)
      *         projection can not be obtained.
      */
-    public GeodeticObjectBuilder setPolarStereographic(final boolean north) 
throws FactoryException {
+    public GeodeticObjectBuilder applyPolarStereographic(final boolean north) 
throws FactoryException {
         setConversionMethod(PolarStereographicA.NAME);
         setConversionName(PolarStereographicA.setParameters(parameters, 
north));
         return this;
@@ -412,7 +476,7 @@ public class GeodeticObjectBuilder extends 
Builder<GeodeticObjectBuilder> {
     }
 
     /**
-     * Creates a projected CRS with base CRS on the specified datum and with 
default axes.
+     * Creates a projected CRS with base CRS on the datum previously specified 
to this builder and with default axes.
      * The base CRS uses the ellipsoid specified by {@link 
#setFlattenedSphere(String, double, double, Unit)}.
      *
      * @return the projected CRS.
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
index a80f26a879..c301310a72 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
@@ -643,7 +643,7 @@ public class CommonAuthorityFactory extends 
GeodeticAuthorityFactory implements
                 if (isUTM && crs != null) {
                     builder.addName(crs.getName());
                 } // else default to the conversion name, which is "Transverse 
Mercator".
-                builder.setTransverseMercator(isUTM ? Zoner.UTM : Zoner.ANY, 
latitude, longitude);
+                builder.applyTransverseMercator(isUTM ? Zoner.UTM : Zoner.ANY, 
latitude, longitude);
             } else {
                 builder.setConversionMethod(method)
                        .addName(PROJECTION_NAMES[projection - 
FIRST_PROJECTION_CODE])
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/GeodeticObjectBuilderTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/GeodeticObjectBuilderTest.java
new file mode 100644
index 0000000000..38f295b7d6
--- /dev/null
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/GeodeticObjectBuilderTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing;
+
+import java.util.function.BiConsumer;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.parameter.ParameterValueGroup;
+import org.apache.sis.measure.Units;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link GeodeticObjectBuilder}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since   1.3
+ * @module
+ */
+public final strictfp class GeodeticObjectBuilderTest extends TestCase {
+    /**
+     * Tests {@link GeodeticObjectBuilder#changeConversion(String, 
BiConsumer)}.
+     *
+     * @throws FactoryException if an operation method name is not supported.
+     */
+    @Test
+    public void testChangeConversion() throws FactoryException {
+        final GeodeticObjectBuilder b = new GeodeticObjectBuilder();
+        assertSame(b, b.setConversionName("Dummy projection"));
+        assertSame(b, b.setConversionMethod("Popular Visualisation Pseudo 
Mercator"));
+        assertSame(b, b.setParameter("Longitude of natural origin", 40, 
Units.DEGREE));
+        assertSame(b, b.setParameter("Scale factor at natural origin", 0.5, 
Units.UNITY));
+        assertSame(b, b.changeConversion("Mercator (Spherical)", null));
+        final ProjectedCRS crs = b.createProjectedCRS();
+        final ParameterValueGroup p = 
crs.getConversionFromBase().getParameterValues();
+        assertEquals(40,  p.parameter("Longitude of natural 
origin").doubleValue(), STRICT);
+        assertEquals(0.5, p.parameter("Scale factor at natural 
origin").doubleValue(), STRICT);
+    }
+}
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 2e5bbe50ee..a689d192ef 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -43,6 +43,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.referencing.ExtentSelectorTest.class,
     org.apache.sis.internal.referencing.WKTKeywordsTest.class,
     org.apache.sis.internal.referencing.WKTUtilitiesTest.class,
+    org.apache.sis.internal.referencing.GeodeticObjectBuilderTest.class,
     org.apache.sis.internal.jaxb.referencing.CodeTest.class,
     org.apache.sis.internal.jaxb.referencing.SecondDefiningParameterTest.class,
 
diff --git 
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
 
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
index 411542f275..9280e64bcf 100644
--- 
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
+++ 
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java
@@ -885,7 +885,7 @@ final class MetadataReader extends MetadataBuilder {
                         || projection.parameter(Constants.FALSE_NORTHING)  
.doubleValue() != 0
                         || 
projection.parameter(Constants.CENTRAL_MERIDIAN).doubleValue() != 0)
                 {
-                    crs = new GeodeticObjectBuilder(listeners.getLocale())
+                    crs = new GeodeticObjectBuilder(factories, 
listeners.getLocale())
                             .addName("Polar 
stereographic").setConversion(projection)
                             .createProjectedCRS(datum.geographic(), 
crs.getCoordinateSystem());
                 }

Reply via email to