You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/09/23 09:44:42 UTC
[sis] 02/02: Add convenience method for deriving a projection from an existing one, and for resampling a grid coverage by specifying only the new CRS.
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 <ma...@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());
}