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 2019/04/13 16:07:35 UTC
[sis] branch geoapi-4.0 updated: Refactor the way we use
ParameterDescriptorGroup, ParameterValueGroup and "grid to target"
transform in GridDatumShift in such a way that we can keep
GridDatumShiftFile internal to NADCON and NTv2 transformations. This avoid
the confusion declaration of ResidualGrid as a subtype of
DatumShiftGridFile. Also moved the DatumShiftGrid.normalizedToGridX/Y
methods as package-private methods in DatumShiftTransform;
those methods should never have been public, since their relationship with
`coordi [...]
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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 69e3bec Refactor the way we use ParameterDescriptorGroup, ParameterValueGroup and "grid to target" transform in GridDatumShift in such a way that we can keep GridDatumShiftFile internal to NADCON and NTv2 transformations. This avoid the confusion declaration of ResidualGrid as a subtype of DatumShiftGridFile. Also moved the DatumShiftGrid.normalizedToGridX/Y methods as package-private methods in DatumShiftTransform; those methods should never have been public, since their relati [...]
69e3bec is described below
commit 69e3bec57ac878fa8ebb39f838281a976e6834a5
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 13 18:03:23 2019 +0200
Refactor the way we use ParameterDescriptorGroup, ParameterValueGroup and "grid to target" transform in GridDatumShift in such a way that we can keep GridDatumShiftFile internal to NADCON and NTv2 transformations. This avoid the confusion declaration of ResidualGrid as a subtype of DatumShiftGridFile.
Also moved the DatumShiftGrid.normalizedToGridX/Y methods as package-private methods in DatumShiftTransform; those methods should never have been public, since their relationship with `coordinateToGrid` conversion can be confusing.
---
.../provider/DatumShiftGridCompressed.java | 4 +-
.../referencing/provider/DatumShiftGridFile.java | 126 ++++--------
.../referencing/provider/DatumShiftGridLoader.java | 4 +-
.../provider/FranceGeocentricInterpolation.java | 2 +-
.../sis/internal/referencing/provider/NADCON.java | 4 +-
.../sis/internal/referencing/provider/NTv2.java | 2 +-
.../sis/referencing/datum/DatumShiftGrid.java | 212 ++++++++++-----------
.../operation/builder/ResidualGrid.java | 141 +++++++++-----
.../operation/transform/DatumShiftTransform.java | 103 +++++++++-
.../transform/InterpolatedGeocentricTransform.java | 15 +-
.../transform/InterpolatedMolodenskyTransform.java | 15 +-
.../operation/transform/InterpolatedTransform.java | 30 +--
.../operation/transform/MolodenskyFormula.java | 4 +-
.../operation/builder/ResidualGridTest.java | 2 +-
.../operation/transform/QuadraticShiftGrid.java | 20 ++
.../java/org/apache/sis/internal/util/Strings.java | 28 ++-
16 files changed, 408 insertions(+), 304 deletions(-)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
index 97cef2e..4eee2d8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
@@ -199,7 +199,7 @@ final class DatumShiftGridCompressed<C extends Quantity<C>, T extends Quantity<T
final int p00 = nx*iy + ix;
final int p10 = nx + p00;
final int n = data.length;
- boolean derivative = (vector.length >= n + 4);
+ boolean derivative = (vector.length >= n + INTERPOLATED_DIMENSIONS*INTERPOLATED_DIMENSIONS);
for (int dim = 0; dim < n; dim++) {
double dx;
final short[] values = data[dim];
@@ -218,7 +218,7 @@ final class DatumShiftGridCompressed<C extends Quantity<C>, T extends Quantity<T
dx++;
} else {
dy++;
- i += 2;
+ i += INTERPOLATED_DIMENSIONS;
derivative = false;
}
vector[i ] = dx;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
index 8953a92..ebc74ee 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
@@ -24,33 +24,21 @@ import javax.measure.Quantity;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.GeneralParameterDescriptor;
-import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.math.DecimalFunctions;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.referencing.datum.DatumShiftGrid;
-import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
-import org.apache.sis.internal.util.Strings;
/**
* A datum shift grid loaded from a file.
* The filename is usually a parameter defined in the EPSG database.
* This class should not be in public API because it requires implementation to expose internal mechanic:
- *
- * <ul>
- * <li>Subclasses need to give an access to their internal data (not a copy) through the {@link #getData()}
- * and {@link #setData(Object[])} methods. We use that for managing the cache, reducing memory usage by
- * sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations.</li>
- * <li>{@link #descriptor}, {@link #gridToTarget()} and {@link #setGridParameters(Parameters)} are convenience
- * members for {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform} constructor.
- * What they do are closely related to how {@code InterpolatedTransform} works, and trying to document that
- * in a public API would probably be too distracting for the users.</li>
- * </ul>
- *
- * The main concrete subclass is {@link DatumShiftGridFile.Float}.
+ * Subclasses need to give an access to their internal data (not a copy) through the {@link #getData()}
+ * and {@link #setData(Object[])} methods. We use that for managing the cache, reducing memory usage by
+ * sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
@@ -64,7 +52,7 @@ import org.apache.sis.internal.util.Strings;
* @since 0.7
* @module
*/
-public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGrid<C,T> {
+abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGrid<C,T> {
/**
* Serial number for inter-operability with different versions.
*/
@@ -88,18 +76,18 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
/**
* The parameter descriptor of the provider that created this grid.
*/
- public final ParameterDescriptorGroup descriptor;
+ private final ParameterDescriptorGroup descriptor;
/**
* The files from which the grid has been loaded. This is not used directly by this class
* (except for {@link #equals(Object)} and {@link #hashCode()}), but can be used by math
- * transform for setting the parameter values. Never empty but may be null if the grid is
- * computed instead than loaded from file(s).
+ * transform for setting the parameter values. Shall never be null and never empty.
*/
private final Path[] files;
/**
* Number of grid cells along the <var>x</var> axis.
+ * This is <code>{@linkplain #getGridSize()}[0]</code> as a field for performance reasons.
*/
protected final int nx;
@@ -112,48 +100,23 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
*
* @see #getCellPrecision()
*/
- protected double accuracy;
+ double accuracy;
/**
* Creates a new datum shift grid for the given grid geometry.
* The actual offset values need to be provided by subclasses.
*
- * @param coordinateUnit the unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}.
- * @param translationUnit the unit of measurement of output values.
- * @param isCellValueRatio {@code true} if results of {@link #interpolateInCell interpolateInCell(…)} are divided by grid cell size.
- * @param coordinateToGrid conversion from the "real world" coordinates to grid indices including fractional parts.
- * @param nx number of cells along the <var>x</var> axis in the grid.
- * @param ny number of cells along the <var>y</var> axis in the grid.
- * @param descriptor the parameter descriptor of the provider that created this grid.
- * @param files the file(s) from which the grid has been loaded.
- *
- * @since 0.8
- */
- protected DatumShiftGridFile(final Unit<C> coordinateUnit,
- final Unit<T> translationUnit,
- final boolean isCellValueRatio,
- final LinearTransform coordinateToGrid,
- final int nx, final int ny,
- final ParameterDescriptorGroup descriptor,
- final Path... files)
- {
- super(coordinateUnit, coordinateToGrid, new int[] {nx, ny}, isCellValueRatio, translationUnit);
- this.descriptor = descriptor;
- this.files = (files.length != 0) ? files : null;
- this.nx = nx;
- this.accuracy = Double.NaN;
- }
-
- /**
- * Creates a new datum shift grid for the given grid geometry.
- * The actual offset values need to be provided by subclasses.
- *
- * @param x0 longitude in degrees of the center of the cell at grid index (0,0).
- * @param y0 latitude in degrees of the center of the cell at grid index (0,0).
- * @param Δx increment in <var>x</var> value between cells at index <var>gridX</var> and <var>gridX</var> + 1.
- * @param Δy increment in <var>y</var> value between cells at index <var>gridY</var> and <var>gridY</var> + 1.
- * @param nx number of cells along the <var>x</var> axis in the grid.
- * @param ny number of cells along the <var>y</var> axis in the grid.
+ * @param coordinateUnit the unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}.
+ * @param translationUnit the unit of measurement of output values.
+ * @param isCellValueRatio {@code true} if results of {@link #interpolateInCell interpolateInCell(…)} are divided by grid cell size.
+ * @param x0 longitude in degrees of the center of the cell at grid index (0,0).
+ * @param y0 latitude in degrees of the center of the cell at grid index (0,0).
+ * @param Δx increment in <var>x</var> value between cells at index <var>gridX</var> and <var>gridX</var> + 1.
+ * @param Δy increment in <var>y</var> value between cells at index <var>gridY</var> and <var>gridY</var> + 1.
+ * @param nx number of cells along the <var>x</var> axis in the grid.
+ * @param ny number of cells along the <var>y</var> axis in the grid.
+ * @param descriptor the parameter descriptor of the provider that created this grid.
+ * @param files the file(s) from which the grid has been loaded. This array is not cloned.
*/
DatumShiftGridFile(final Unit<C> coordinateUnit,
final Unit<T> translationUnit,
@@ -164,8 +127,12 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
final ParameterDescriptorGroup descriptor,
final Path... files) throws NoninvertibleTransformException
{
- this(coordinateUnit, translationUnit, isCellValueRatio,
- new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(), nx, ny, descriptor, files);
+ super(coordinateUnit, new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(),
+ new int[] {nx, ny}, isCellValueRatio, translationUnit);
+ this.descriptor = descriptor;
+ this.files = files;
+ this.nx = nx;
+ this.accuracy = Double.NaN;
}
/**
@@ -248,17 +215,13 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
}
/**
- * Returns the transform from grid coordinates to "real world" coordinates after the datum shift has been applied,
- * or {@code null} for the default. This is usually the inverse of the transform from "real world" coordinates to
- * grid coordinates before datum shift, since NADCON and NTv2 transformations have source and target coordinates
- * in the same coordinate system (with axis units in degrees). But this method may be overridden by subclasses that
- * use {@code DatumShiftGridFile} for other kind of transformations.
+ * Returns the descriptor specified at construction time.
*
- * @return the transformation from grid coordinates to "real world" coordinates after datum shift,
- * or {@code null} for the default (namely the inverse of the "source to grid" transformation).
+ * @return a description of the values in this grid.
*/
- public Matrix gridToTarget() {
- return null;
+ @Override
+ public final ParameterDescriptorGroup getParameterDescriptors() {
+ return descriptor;
}
/**
@@ -267,16 +230,15 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
*
* @param parameters the parameter group where to set the values.
*/
- public void setGridParameters(final Parameters parameters) {
- if (files != null) {
- int i = 0;
- for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
- if (gd instanceof ParameterDescriptor<?>) {
- final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
- if (Path.class.isAssignableFrom(d.getValueClass())) {
- parameters.getOrCreate(d).setValue(files[i]);
- if (++i == files.length) break;
- }
+ @Override
+ public final void getParameterValues(final Parameters parameters) {
+ int i = 0;
+ for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
+ if (gd instanceof ParameterDescriptor<?>) {
+ final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
+ if (Path.class.isAssignableFrom(d.getValueClass())) {
+ if (i >= files.length) break; // Safety in case of invalid parameters.
+ parameters.getOrCreate(d).setValue(files[i++]);
}
}
}
@@ -311,16 +273,6 @@ public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quanti
return super.hashCode() + Arrays.hashCode(files);
}
- /**
- * Returns a string representation of this grid.
- *
- * @return a string representation for debugging purpose.
- */
- @Override
- public String toString() {
- return Strings.toString(getClass(), "file", (files != null) ? files[0] : null);
- }
-
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java
index 7166426..f701d51 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java
@@ -65,7 +65,7 @@ class DatumShiftGridLoader {
static final double SECOND_PRECISION = 1E-4;
/**
- * The file to load, used only if we have errors to report.
+ * The file to load, used for parameter declaration and if we have errors to report.
*/
final Path file;
@@ -84,7 +84,7 @@ class DatumShiftGridLoader {
*
* @param channel where to read data from.
* @param buffer the buffer to use.
- * @param file path to the longitude or latitude difference file. Used only for error reporting.
+ * @param file path to the longitude or latitude difference file. Used for parameter declaration and error reporting.
*/
DatumShiftGridLoader(final ReadableByteChannel channel, final ByteBuffer buffer, final Path file) throws IOException {
this.file = file;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
index 347c78e..32a0734 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java
@@ -350,7 +350,7 @@ public class FranceGeocentricInterpolation extends GeodeticOperation {
* Unconditionally loads the grid for the given file without in-memory compression.
*
* @param in reader of the RGF93 datum shift file.
- * @param file path to the file being read, used only for error reporting.
+ * @param file path to the file being read, used for parameter declaration and error reporting.
* @throws IOException if an I/O error occurred.
* @throws NumberFormatException if a number can not be parsed.
* @throws NoSuchElementException if a data line is missing a value.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java
index 860e45c..3503b1c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java
@@ -262,7 +262,7 @@ public final class NADCON extends AbstractProvider {
* @param channel where to read data from.
* @param buffer the buffer to use. That buffer must use little endian byte order
* and have a capacity divisible by the size of the {@code float} type.
- * @param file path to the longitude or latitude difference file. Used only for error reporting.
+ * @param file path to the longitude or latitude difference file. Used for parameter declaration and error reporting.
*/
Loader(final ReadableByteChannel channel, final ByteBuffer buffer, final Path file)
throws IOException, FactoryException
@@ -376,7 +376,7 @@ public final class NADCON extends AbstractProvider {
*
* @param fb a {@code FloatBuffer} view over the full {@link #buffer} range.
* @param latitudeShifts the previously loaded latitude shifts, or {@code null} if not yet loaded.
- * @param longitudeShifts the file for the longitude grid, or {@code null} if identical to {@link #file}.
+ * @param longitudeShifts the file for the longitude grid.
*/
final void readGrid(final FloatBuffer fb, final Loader latitudeShifts, final Path longitudeShifts)
throws IOException, FactoryException, NoninvertibleTransformException
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java
index f6bdf37..ba3c018 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java
@@ -247,7 +247,7 @@ public final class NTv2 extends AbstractProvider {
* This constructor parses the header immediately, but does not read any grid.
*
* @param channel where to read data from.
- * @param file path to the longitude and latitude difference file. Used only for error reporting.
+ * @param file path to the longitude and latitude difference file. Used for parameter declaration and error reporting.
* @throws FactoryException if a data record can not be parsed.
*/
Loader(final ReadableByteChannel channel, final Path file) throws IOException, FactoryException {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
index 1c3cdd3..a34d548 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DatumShiftGrid.java
@@ -18,18 +18,18 @@ package org.apache.sis.referencing.datum;
import java.util.Arrays;
import java.util.Objects;
-import java.io.IOException;
import java.io.Serializable;
-import java.io.ObjectInputStream;
import javax.measure.Unit;
import javax.measure.Quantity;
import org.opengis.geometry.Envelope;
+import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.parameter.Parameters;
import org.apache.sis.internal.util.DoubleDouble;
import org.apache.sis.internal.util.Strings;
import org.apache.sis.util.resources.Errors;
@@ -41,7 +41,7 @@ import org.apache.sis.measure.Units;
* Small but non-constant translations to apply on coordinates for datum shifts or other transformation process.
* The main purpose of this class is to encapsulate the data provided by <cite>datum shift grid files</cite>
* like NTv2, NADCON or RGF93. But this class could also be used for other kind of transformations,
- * provided that the shifts are <strong>small</strong> (otherwise algorithms may not converge).
+ * provided that the shifts are relatively small (otherwise algorithms may not converge).
*
* <p>{@linkplain DefaultGeodeticDatum Geodetic datum} changes can be implemented by translations in geographic
* or geocentric coordinates. Translations given by {@code DatumShiftGrid} instances are often, but not always,
@@ -101,13 +101,14 @@ import org.apache.sis.measure.Units;
* <div class="section">Number of dimensions</div>
* Input coordinates and translation vectors can have any number of dimensions. However in the current implementation,
* only the two first dimensions are used for interpolating the translation vectors. This restriction appears in the
- * following method signatures:
+ * following field and method signatures:
*
* <ul>
- * <li>{@link #interpolateInCell(double, double, double[])}
- * where the two first {@code double} values are (<var>x</var>,<var>y</var>) grid indices.</li>
+ * <li>{@link #INTERPOLATED_DIMENSIONS}.</li>
* <li>{@link #getCellValue(int, int, int)}
* where the two last {@code int} values are (<var>x</var>,<var>y</var>) grid indices.</li>
+ * <li>{@link #interpolateInCell(double, double, double[])}
+ * where the two first {@code double} values are (<var>x</var>,<var>y</var>) grid indices.</li>
* <li>{@link #derivativeInCell(double, double)}
* where the values are (<var>x</var>,<var>y</var>) grid indices.</li>
* </ul>
@@ -141,10 +142,22 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
private static final long serialVersionUID = 8405276545243175808L;
/**
- * Number of dimensions in which interpolations are applied. The grid may have more dimensions,
- * but only this number of dimensions will be used in interpolations.
+ * Number of source dimensions in which interpolations are applied. The grids may have more dimensions,
+ * but only this number of dimensions will be used in interpolations. The value of this field is set to
+ * {@value}. That value is hard-coded not only in this field, but also in signature of various methods
+ * expecting a two-dimensional (<var>x</var>, <var>y</var>) position:
+ * <code>{@linkplain #getCellValue(int, int, int) getCellValue}(…, x, y)</code>,
+ * <code>{@linkplain #interpolateInCell(double, double, double[]) interpolateInCell}(x, y, …)</code>,
+ * <code>{@linkplain #derivativeInCell(double, double) derivativeInCell}(x, y)</code>.
+ *
+ * <div class="note"><b>Future evolution:</b>
+ * if this class is generalized to more source dimensions in a future Apache SIS version, then this field
+ * may be deprecated or its value changed. That change would be accompanied by new methods with different
+ * signature. This field can be used as a way to detect that such change occurred.</div>
+ *
+ * @since 1.0
*/
- private static final int INTERPOLATED_DIMENSIONS = 2;
+ protected static final int INTERPOLATED_DIMENSIONS = 2;
/**
* The unit of measurements of input values, before conversion to grid indices by {@link #coordinateToGrid}.
@@ -186,18 +199,6 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
private final int[] gridSize;
/**
- * Conversion from (λ,φ) coordinates in radians to grid indices (x,y).
- *
- * <ul>
- * <li>x = (λ - λ₀) ⋅ {@code scaleX} = λ ⋅ {@code scaleX} + x₀</li>
- * <li>y = (φ - φ₀) ⋅ {@code scaleY} = φ ⋅ {@code scaleY} + y₀</li>
- * </ul>
- *
- * Those factors are extracted from the {@link #coordinateToGrid} transform for performance purposes.
- */
- private transient double scaleX, scaleY, x0, y0;
-
- /**
* Creates a new datum shift grid for the given size and units.
* The actual cell values need to be provided by subclasses.
*
@@ -233,49 +234,6 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
: Errors.Keys.IllegalArgumentValue_2, Strings.toIndexed("gridSize", i), n));
}
}
- computeConversionFactors();
- }
-
- /**
- * Computes the conversion factors needed by {@link #interpolateInCell(double, double, double[])}.
- * This method takes only the {@value #INTERPOLATED_DIMENSIONS} first dimensions. If a conversion
- * factor can not be computed, then it is set to NaN.
- */
- @SuppressWarnings("fallthrough")
- private void computeConversionFactors() {
- scaleX = Double.NaN;
- scaleY = Double.NaN;
- x0 = Double.NaN;
- y0 = Double.NaN;
- final double toStandardUnit = Units.toStandardUnit(coordinateUnit);
- if (!Double.isNaN(toStandardUnit)) {
- final Matrix m = coordinateToGrid.getMatrix();
- if (Matrices.isAffine(m)) {
- final int n = m.getNumCol() - 1;
- switch (m.getNumRow()) {
- default: y0 = m.getElement(1,n); scaleY = diagonal(m, 1, n) / toStandardUnit; // Fall through
- case 1: x0 = m.getElement(0,n); scaleX = diagonal(m, 0, n) / toStandardUnit;
- case 0: break;
- }
- }
- }
- }
-
- /**
- * Returns the value on the diagonal of the given matrix, provided that all other non-translation terms are 0.
- *
- * @param m the matrix from which to get the scale factor on a row.
- * @param j the row for which to get the scale factor.
- * @param n index of the last column.
- * @return the scale factor on the diagonal, or NaN.
- */
- private static double diagonal(final Matrix m, final int j, int n) {
- while (--n >= 0) {
- if (j != n && m.getElement(j, n) != 0) {
- return Double.NaN;
- }
- }
- return m.getElement(j, j);
}
/**
@@ -290,15 +248,15 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
isCellValueRatio = other.isCellValueRatio;
translationUnit = other.translationUnit;
gridSize = other.gridSize;
- scaleX = other.scaleX;
- scaleY = other.scaleY;
- x0 = other.x0;
- y0 = other.y0;
}
/**
* Returns the number of cells along each axis in the grid.
- * The length of this array is equal to {@code coordinateToGrid} target dimensions.
+ * The length of this array is the number of grid dimensions, which is typically {@value #INTERPOLATED_DIMENSIONS}.
+ * The grid dimensions shall be equal to {@link #getCoordinateToGrid() coordinateToGrid} target dimensions.
+ *
+ * <div class="note"><b>Note:</b> the number of grid dimensions is not necessarily equal to the
+ * {@linkplain #getTranslationDimensions() number of dimension of the translation vectors}.</div>
*
* @return the number of cells along each axis in the grid.
*/
@@ -339,10 +297,11 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
}
/**
- * Conversion from the "real world" coordinates to grid indices including fractional parts.
- * The input points given to the {@code MathTransform} shall be in the unit of measurement
- * given by {@link #getCoordinateUnit()}.
- * The output points are grid indices with integer values in the center of grid cells.
+ * Returns the conversion from the source coordinates (in "real world" units) to grid indices.
+ * The input coordinates given to the {@link LinearTransform} shall be in the unit of measurement
+ * given by {@link #getCoordinateUnit()}. The output coordinates are grid indices as real numbers
+ * (i.e. can have a fractional part). Integer grid indices are located in the center of grid cells,
+ * i.e. the transform uses {@link org.opengis.referencing.datum.PixelInCell#CELL_CENTER} convention.
*
* <p>This transform is usually two-dimensional, in which case conversions from (<var>x</var>,<var>y</var>)
* coordinates to ({@code gridX}, {@code gridY}) indices can be done with the following formulas:</p>
@@ -375,30 +334,6 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
}
/**
- * Converts the given normalized <var>x</var> coordinate to grid index.
- * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#getSystemUnit()}.
- * For angular coordinates, this is radians. For linear coordinates, this is metres.
- *
- * @param x the "real world" coordinate (often longitude in radians) of the point for which to get the translation.
- * @return the grid index for the given coordinate. May be out of bounds.
- */
- public final double normalizedToGridX(final double x) {
- return x * scaleX + x0;
- }
-
- /**
- * Converts the given normalized <var>x</var> coordinate to grid index.
- * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#getSystemUnit()}.
- * For angular coordinates, this is radians. For linear coordinates, this is metres.
- *
- * @param y the "real world" coordinate (often latitude in radians) of the point for which to get the translation.
- * @return the grid index for the given coordinate. May be out of bounds.
- */
- public final double normalizedToGridY(final double y) {
- return y * scaleY + y0;
- }
-
- /**
* Returns the number of dimensions of the translation vectors interpolated by this datum shift grid.
* This number of dimensions is not necessarily equals to the number of source or target dimensions
* of the "{@linkplain #getCoordinateToGrid() coordinate to grid}" transform.
@@ -688,7 +623,78 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
}
/**
+ * Returns a description of the values in this grid. Grid values may be given directly as matrices or tensors,
+ * or indirectly as name of file from which data were loaded. If grid values are given directly, then:
+ *
+ * <ul>
+ * <li>The number of {@linkplain #getGridSize() grid} dimensions determines the parameter type:
+ * one-dimensional grids are represented by {@link org.apache.sis.math.Vector} instances,
+ * two-dimensional grids are represented by {@link Matrix} instances,
+ * and grids with more than {@value #INTERPOLATED_DIMENSIONS} are represented by tensors.</li>
+ * <li>The {@linkplain #getTranslationDimensions() number of dimensions of translation vectors}
+ * determines how many matrix or tensor parameters appear.</li>
+ * </ul>
+ *
+ * <div class="note"><b>Example 1:</b>
+ * if this {@code DatumShiftGrid} instance has been created for performing NADCON datum shifts,
+ * then this method returns a group named "NADCON" with two parameters:
+ * <ul>
+ * <li>A parameter of type {@link java.nio.file.Path} named “Latitude difference file”.</li>
+ * <li>A parameter of type {@link java.nio.file.Path} named “Longitude difference file”.</li>
+ * </ul></div>
+ *
+ * <div class="note"><b>Example 2:</b>
+ * if this {@code DatumShiftGrid} instance has been created by
+ * {@link org.apache.sis.referencing.operation.builder.LocalizationGridBuilder},
+ * then this method returns a group named "Localization grid" with four parameters:
+ * <ul>
+ * <li>A parameter of type {@link Integer} named “num_row” for the number of rows in each matrix.</li>
+ * <li>A parameter of type {@link Integer} named “num_col” for the number of columns in each matrix.</li>
+ * <li>A parameter of type {@link Matrix} named “grid_x”.</li>
+ * <li>A parameter of type {@link Matrix} named “grid_y”.</li>
+ * </ul></div>
+ *
+ * @return a description of the values in this grid.
+ *
+ * @since 1.0
+ */
+ public abstract ParameterDescriptorGroup getParameterDescriptors();
+
+ /**
+ * Gets the parameter values for the grids and stores them in the provided {@code parameters} group.
+ * The given {@code parameters} must have the descriptor returned by {@link #getParameterDescriptors()}.
+ * The matrices, tensors or file names are stored in the given {@code parameters} instance.
+ *
+ * <div class="note"><b>Implementation note:</b>
+ * this method is invoked by {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform}
+ * and other transforms for initializing the values of their parameter group.</div>
+ *
+ * @param parameters the parameter group where to set the values.
+ *
+ * @since 1.0
+ */
+ public abstract void getParameterValues(Parameters parameters);
+
+ /**
+ * Returns a string representation of this {@code DatumShiftGrid}. The default implementation
+ * formats the {@linkplain #getParameterValues(Parameters) parameter values}.
+ *
+ * @return a string representation of the grid parameters.
+ *
+ * @since 1.0
+ */
+ @Override
+ public String toString() {
+ final Parameters p = Parameters.castOrWrap(getParameterDescriptors().createValue());
+ getParameterValues(p);
+ return p.toString();
+ }
+
+ /**
* Returns {@code true} if the given object is a grid containing the same data than this grid.
+ * Default implementation compares only the properties known to this abstract class like
+ * {@linkplain #getGridSize() grid size}, {@linkplain #getCoordinateUnit() coordinate unit}, <i>etc.</i>
+ * Subclasses need to override for adding comparison of the actual values.
*
* @param other the other object to compare with this datum shift grid.
* @return {@code true} if the given object is non-null, of the same class than this {@code DatumShiftGrid}
@@ -718,16 +724,4 @@ public abstract class DatumShiftGrid<C extends Quantity<C>, T extends Quantity<T
public int hashCode() {
return Objects.hashCode(coordinateToGrid) + 37 * Arrays.hashCode(gridSize);
}
-
- /**
- * Invoked after deserialization. This method computes the transient fields.
- *
- * @param in the input stream from which to deserialize the datum shift grid.
- * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
- * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
- */
- private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- computeConversionFactors();
- }
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
index 4e27f04..4cf8672 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ResidualGrid.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.referencing.operation.builder;
+import java.util.Arrays;
import java.util.function.Function;
import javax.measure.quantity.Dimensionless;
import org.opengis.parameter.ParameterDescriptor;
@@ -23,10 +24,10 @@ import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.referencing.operation.Matrix;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.parameter.ParameterBuilder;
-import org.apache.sis.referencing.operation.matrix.Matrix3;
+import org.apache.sis.referencing.datum.DatumShiftGrid;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.ContextualParameters;
-import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.internal.util.Numerics;
@@ -45,27 +46,29 @@ import org.apache.sis.measure.Units;
* @since 0.8
* @module
*/
-final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless> {
+final class ResidualGrid extends DatumShiftGrid<Dimensionless,Dimensionless> {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = 1445697681304159019L;
+ private static final long serialVersionUID = -713276314000661839L;
/**
* Number of source dimensions of the residual grid.
+ *
+ * @see #INTERPOLATED_DIMENSIONS
*/
static final int SOURCE_DIMENSION = 2;
/**
* The parameter descriptors for the "Localization grid" operation.
- * Current implementation is fixed to 2 dimensions. This is not a committed set of parameters and they
- * may change in any future SIS version. We define them mostly for {@code toString()} implementation.
+ * Current implementation is fixed to {@value #SOURCE_DIMENSION} dimensions.
+ *
+ * @see #getParameterDescriptors()
*/
private static final ParameterDescriptorGroup PARAMETERS;
static {
final ParameterBuilder builder = new ParameterBuilder().setRequired(true);
- @SuppressWarnings("rawtypes")
- final ParameterDescriptor<?>[] grids = new ParameterDescriptor[] {
+ final ParameterDescriptor<?>[] grids = new ParameterDescriptor<?>[] {
builder.addName(Constants.NUM_ROW).createBounded(Integer.class, 2, null, null),
builder.addName(Constants.NUM_COL).createBounded(Integer.class, 2, null, null),
builder.addName("grid_x").create(Matrix.class, null),
@@ -80,13 +83,17 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
* This method sets the matrix parameters using views over the {@link #offsets} array.
*/
@Override
- public void setGridParameters(final Parameters parameters) {
- super.setGridParameters(parameters);
- final Matrix denormalization;
+ public void getParameterValues(final Parameters parameters) {
+ final Matrix denormalization = gridToTarget.getMatrix();
if (parameters instanceof ContextualParameters) {
- denormalization = ((ContextualParameters) parameters).getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
- } else {
- denormalization = new Matrix3(); // Identity.
+ /*
+ * The denormalization matrix computed by InterpolatedTransform is the inverse of the normalization matrix.
+ * This inverse is not suitable for the transform created by LocalizationGridBuilder; we need to replace it
+ * by the linear regression. We do not want to define a public API in `DatumShiftGrid` for that purpose yet
+ * because it would complexify that class (we would have to define API contract, etc.).
+ */
+ MatrixSIS m = ((ContextualParameters) parameters).getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+ m.setMatrix(denormalization);
}
final int[] size = getGridSize();
parameters.parameter(Constants.NUM_ROW).setValue(size[1]);
@@ -96,17 +103,32 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
}
/**
+ * Number of grid cells along the <var>x</var> axis.
+ * This is <code>{@linkplain #getGridSize()}[0]</code> as a field for performance reasons.
+ */
+ private final int nx;
+
+ /**
* The residual data, as translations to apply on the result of affine transform.
* In this flat array, index of target dimension varies fastest, then column index, then row index.
*/
private final double[] offsets;
/**
- * Conversion from grid coordinates to the final "real world" coordinates.
+ * Conversion from translated coordinates (after the datum shift has been applied) to "real world" coordinates.
+ * If we were doing NADCON or NTv2 transformations with {@link #isCellValueRatio()} = {@code true} (source and
+ * target coordinates in the same coordinate system with axis units in degrees), that conversion would be the
+ * inverse of {@link #getCoordinateToGrid()}. But in this {@code ResidualGrid} case, we need to override with
+ * the linear regression computed by {@link LocalizationGridBuilder}.
+ */
+ final LinearTransform gridToTarget;
+
+ /**
+ * The best translation accuracy that we can expect from this file.
*
- * @see #gridToTarget()
+ * @see #getCellPrecision()
*/
- private final LinearTransform gridToTarget;
+ private final double accuracy;
/**
* Creates a new residual grid.
@@ -119,46 +141,29 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
ResidualGrid(final LinearTransform sourceToGrid, final LinearTransform gridToTarget,
final int nx, final int ny, final double[] residuals, final double precision)
{
- super(Units.UNITY, Units.UNITY, true, sourceToGrid, nx, ny, PARAMETERS);
+ super(Units.UNITY, sourceToGrid, new int[] {nx, ny}, true, Units.UNITY);
this.gridToTarget = gridToTarget;
this.offsets = residuals;
this.accuracy = precision;
+ this.nx = nx;
}
/**
- * Creates a new datum shift grid with the same grid geometry than the given grid
- * but a reference to a different data array.
- */
- private ResidualGrid(final ResidualGrid other, final double[] data) {
- super(other);
- gridToTarget = other.gridToTarget;
- accuracy = other.accuracy;
- offsets = data;
- }
-
- /**
- * Returns a new grid with the same geometry than this grid but different data array.
- */
- @Override
- protected DatumShiftGridFile<Dimensionless, Dimensionless> setData(final Object[] other) {
- return new ResidualGrid(this, (double[]) other[0]);
- }
-
- /**
- * Returns reference to the data array. This method is for cache management, {@link #equals(Object)}
- * and {@link #hashCode()} implementations only and should not be invoked in other context.
- */
- @Override
- protected Object[] getData() {
- return new Object[] {offsets};
- }
-
- /**
- * Returns the transform from grid coordinates to "real world" coordinates after the datum shift has been applied.
+ * Returns a description of the values in this grid. Grid values may be given as matrices or tensors.
+ * Current implementation provides values in the form of {@link Matrix} objects on the assumption
+ * that the number of {@linkplain #getGridSize() grid} dimensions is {@value #SOURCE_DIMENSION}.
+ *
+ * <div class="note"><b>Note:</b>
+ * the number of {@linkplain #getGridSize() grid} dimensions determines the parameter type: if that number
+ * is greater than {@value #SOURCE_DIMENSION}, then parameters would need to be represented by tensors instead
+ * than matrices. By contrast, the {@linkplain #getTranslationDimensions() number of dimensions of translation
+ * vectors} only determines how many matrix or tensor parameters appear.</div>
+ *
+ * @return a description of the values in this grid.
*/
@Override
- public Matrix gridToTarget() {
- return gridToTarget.getMatrix();
+ public ParameterDescriptorGroup getParameterDescriptors() {
+ return PARAMETERS;
}
/**
@@ -189,8 +194,19 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
}
/**
- * View over one dimension of the offset vectors. This is used for populating the {@link ParameterDescriptorGroup}
+ * View over one target dimension of the localization grid. Used for populating the {@link ParameterDescriptorGroup}
* that describes the {@code MathTransform}. Those parameters are themselves used for formatting Well Known Text.
+ * Current implementation can be used only when the number of grid dimensions is {@value #INTERPOLATED_DIMENSIONS}.
+ * If a grid has more dimensions, then tensors would need to be used instead than matrices.
+ *
+ * <p>This implementation can not be moved to the {@link DatumShiftGrid} parent class because this class assumes
+ * that the translation vectors are added to the source coordinates. This is not always true; for example France
+ * Geocentric interpolations add the translation to coordinates converted to geocentric coordinates.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
*/
private final class Data extends FormattableObject implements Matrix, Function<int[],Number> {
/** Coefficients from the denormalization matrix for the row corresponding to this dimension. */
@@ -274,4 +290,29 @@ final class ResidualGrid extends DatumShiftGridFile<Dimensionless,Dimensionless>
return "Matrix";
}
}
+
+ /**
+ * Returns {@code true} if the given object is a grid containing the same data than this grid.
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) { // Optimization for a common case.
+ return true;
+ }
+ if (super.equals(other)) {
+ final ResidualGrid that = (ResidualGrid) other;
+ return Numerics.equals(accuracy, that.accuracy) &&
+ gridToTarget.equals(that.gridToTarget) &&
+ Arrays.equals(offsets, that.offsets);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a hash code value for this datum shift grid.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Arrays.hashCode(offsets) + 37 * gridToTarget.hashCode();
+ }
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java
index 3acb776..74cfb4e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DatumShiftTransform.java
@@ -18,16 +18,21 @@ package org.apache.sis.referencing.operation.transform;
import java.util.Objects;
import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
import javax.measure.Unit;
import javax.measure.quantity.Length;
import javax.measure.UnitConverter;
import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.operation.Matrix;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.geometry.MismatchedDimensionException;
import org.apache.sis.referencing.datum.DatumShiftGrid;
+import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.provider.Molodensky;
+import org.apache.sis.measure.Units;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Debug;
@@ -68,7 +73,7 @@ import org.apache.sis.util.Debug;
* SIS handles those datum shifts with the {@link InterpolatedTransform} subclass.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.0
*
* @see DatumShiftGrid
*
@@ -96,6 +101,19 @@ public abstract class DatumShiftTransform extends AbstractMathTransform implemen
final DatumShiftGrid<?,?> grid;
/**
+ * Conversion from (λ,φ) coordinates in radians to grid indices (x,y).
+ *
+ * <ul>
+ * <li>x = (λ - λ₀) ⋅ {@code scaleX} = λ ⋅ {@code scaleX} + x₀</li>
+ * <li>y = (φ - φ₀) ⋅ {@code scaleY} = φ ⋅ {@code scaleY} + y₀</li>
+ * </ul>
+ *
+ * Those factors are extracted from the {@link DatumShiftGrid#getCoordinateToGrid()}
+ * transform for performance reasons.
+ */
+ private transient double scaleX, scaleY, x0, y0;
+
+ /**
* Creates a datum shift transform for direct interpolations in a grid.
* It is caller responsibility to initialize the {@link #context} parameters.
*
@@ -106,6 +124,7 @@ public abstract class DatumShiftTransform extends AbstractMathTransform implemen
final int size = grid.getTranslationDimensions() + 1;
context = new ContextualParameters(descriptor, size, size);
this.grid = grid;
+ computeConversionFactors();
}
/**
@@ -122,6 +141,64 @@ public abstract class DatumShiftTransform extends AbstractMathTransform implemen
{
context = new ContextualParameters(descriptor, isSource3D ? 4 : 3, isTarget3D ? 4 : 3);
this.grid = grid;
+ computeConversionFactors();
+ }
+
+ /**
+ * Invoked after deserialization. This method computes the transient fields.
+ *
+ * @param in the input stream from which to deserialize the datum shift grid.
+ * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
+ * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ computeConversionFactors();
+ }
+
+ /**
+ * Computes the conversion factors needed for calls to {@link DatumShiftGrid#interpolateInCell(double, double, double[])}.
+ * This method takes only the {@value DatumShiftGrid#INTERPOLATED_DIMENSIONS} first dimensions. If a conversion factor can
+ * not be computed, then it is set to NaN.
+ */
+ @SuppressWarnings("fallthrough")
+ private void computeConversionFactors() {
+ scaleX = Double.NaN;
+ scaleY = Double.NaN;
+ x0 = Double.NaN;
+ y0 = Double.NaN;
+ if (grid != null) {
+ final LinearTransform coordinateToGrid = grid.getCoordinateToGrid();
+ final double toStandardUnit = Units.toStandardUnit(grid.getCoordinateUnit());
+ if (!Double.isNaN(toStandardUnit)) {
+ final Matrix m = coordinateToGrid.getMatrix();
+ if (Matrices.isAffine(m)) {
+ final int n = m.getNumCol() - 1;
+ switch (m.getNumRow()) {
+ default: y0 = m.getElement(1,n); scaleY = diagonal(m, 1, n) / toStandardUnit; // Fall through
+ case 1: x0 = m.getElement(0,n); scaleX = diagonal(m, 0, n) / toStandardUnit;
+ case 0: break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the value on the diagonal of the given matrix, provided that all other non-translation terms are 0.
+ *
+ * @param m the matrix from which to get the scale factor on a row.
+ * @param j the row for which to get the scale factor.
+ * @param n index of the last column.
+ * @return the scale factor on the diagonal, or NaN.
+ */
+ private static double diagonal(final Matrix m, final int j, int n) {
+ while (--n >= 0) {
+ if (j != n && m.getElement(j, n) != 0) {
+ return Double.NaN;
+ }
+ }
+ return m.getElement(j, j);
}
/**
@@ -221,6 +298,30 @@ public abstract class DatumShiftTransform extends AbstractMathTransform implemen
}
/**
+ * Converts the given normalized <var>x</var> coordinate to grid index.
+ * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#getSystemUnit()}.
+ * For angular coordinates, this is radians. For linear coordinates, this is metres.
+ *
+ * @param x the "real world" coordinate (often longitude in radians) of the point for which to get the translation.
+ * @return the grid index for the given coordinate. May be out of bounds.
+ */
+ final double normalizedToGridX(final double x) {
+ return x * scaleX + x0;
+ }
+
+ /**
+ * Converts the given normalized <var>x</var> coordinate to grid index.
+ * "Normalized coordinates" are coordinates in the unit of measurement given by {@link Unit#getSystemUnit()}.
+ * For angular coordinates, this is radians. For linear coordinates, this is metres.
+ *
+ * @param y the "real world" coordinate (often latitude in radians) of the point for which to get the translation.
+ * @return the grid index for the given coordinate. May be out of bounds.
+ */
+ final double normalizedToGridY(final double y) {
+ return y * scaleY + y0;
+ }
+
+ /**
* {@inheritDoc}
*
* @return {@inheritDoc}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
index 6962dbe..170bc57 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
@@ -28,7 +28,6 @@ import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation;
import org.apache.sis.internal.referencing.provider.Molodensky;
import org.apache.sis.internal.util.Constants;
@@ -79,7 +78,7 @@ import org.apache.sis.util.ArgumentChecks;
*
* @author Simon Reynard (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.0
*
* @see InterpolatedMolodenskyTransform
*
@@ -235,9 +234,7 @@ public class InterpolatedGeocentricTransform extends DatumShiftTransform {
semiMinor = source.getSemiMinorAxis();
setContextParameters(semiMajor, semiMinor, unit, target);
context.getOrCreate(Molodensky.DIMENSION).setValue(isSource3D ? 3 : 2);
- if (grid instanceof DatumShiftGridFile<?,?>) {
- ((DatumShiftGridFile<?,?>) grid).setGridParameters(context);
- }
+ grid.getParameterValues(context);
/*
* The above setContextParameters(…) method converted the axis lengths of target ellipsoid in the same units
* than source ellipsoid. Opportunistically fetch that value, so we don't have to convert the values ourselves.
@@ -375,8 +372,8 @@ public class InterpolatedGeocentricTransform extends DatumShiftTransform {
* The translation that we got is in metres, which we convert into normalized units.
*/
final double[] vector = new double[3];
- grid.interpolateInCell(grid.normalizedToGridX(srcPts[srcOff]), // In radians
- grid.normalizedToGridY(srcPts[srcOff+1]), vector);
+ grid.interpolateInCell(normalizedToGridX(srcPts[srcOff]), // In radians
+ normalizedToGridY(srcPts[srcOff+1]), vector);
final double tX = vector[0] / semiMajor;
final double tY = vector[1] / semiMajor;
final double tZ = vector[2] / semiMajor;
@@ -522,8 +519,8 @@ public class InterpolatedGeocentricTransform extends DatumShiftTransform {
* geocentric interpolation at that location and get the (λ,φ) again. In theory, we just
* iterate until we got the desired precision. But in practice a single interation is enough.
*/
- grid.interpolateInCell(grid.normalizedToGridX(vector[0]),
- grid.normalizedToGridY(vector[1]), vector);
+ grid.interpolateInCell(normalizedToGridX(vector[0]),
+ normalizedToGridY(vector[1]), vector);
vector[0] = (x - vector[0] / semiMajor) * scale;
vector[1] = (y - vector[1] / semiMajor) * scale;
vector[2] = (z - vector[2] / semiMajor) * scale;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
index 65e3b26..d731805 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransform.java
@@ -28,7 +28,6 @@ import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation;
import org.apache.sis.internal.referencing.provider.Molodensky;
import org.apache.sis.internal.util.Constants;
@@ -61,7 +60,7 @@ import org.apache.sis.util.Debug;
* ({@linkplain #tX}, {@linkplain #tY}, {@linkplain #tZ}) parameters of a Molodensky transformation.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
*
* @see InterpolatedGeocentricTransform
*
@@ -223,9 +222,7 @@ public class InterpolatedMolodenskyTransform extends MolodenskyFormula {
pg.getOrCreate(Molodensky.AXIS_LENGTH_DIFFERENCE).setValue(Δa, unit);
pg.getOrCreate(Molodensky.FLATTENING_DIFFERENCE) .setValue(Δf, Units.UNITY);
}
- if (grid instanceof DatumShiftGridFile<?,?>) {
- ((DatumShiftGridFile<?,?>) grid).setGridParameters(pg);
- }
+ grid.getParameterValues(pg);
}
/**
@@ -244,8 +241,8 @@ public class InterpolatedMolodenskyTransform extends MolodenskyFormula {
final double[] vector = new double[3];
final double λ = srcPts[srcOff];
final double φ = srcPts[srcOff+1];
- grid.interpolateInCell(grid.normalizedToGridX(λ),
- grid.normalizedToGridY(φ), vector);
+ grid.interpolateInCell(normalizedToGridX(λ),
+ normalizedToGridY(φ), vector);
return transform(λ, φ, isSource3D ? srcPts[srcOff+2] : 0,
dstPts, dstOff, vector[0], vector[1], vector[2], null, derivate);
}
@@ -292,8 +289,8 @@ public class InterpolatedMolodenskyTransform extends MolodenskyFormula {
while (--numPts >= 0) {
final double λ = srcPts[srcOff ];
final double φ = srcPts[srcOff+1];
- grid.interpolateInCell(grid.normalizedToGridX(λ),
- grid.normalizedToGridY(φ), offset);
+ grid.interpolateInCell(normalizedToGridX(λ),
+ normalizedToGridY(φ), offset);
transform(λ, φ, isSource3D ? srcPts[srcOff+2] : 0,
dstPts, dstOff, offset[0], offset[1], offset[2], null, false);
srcOff += srcInc;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
index 8fc5bcc..62ccf8b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/InterpolatedTransform.java
@@ -37,8 +37,6 @@ import org.apache.sis.util.ComparisonMode;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.internal.referencing.DirectPositionView;
-import org.apache.sis.internal.referencing.provider.NTv2;
-import org.apache.sis.internal.referencing.provider.DatumShiftGridFile;
/**
@@ -132,14 +130,11 @@ public class InterpolatedTransform extends DatumShiftTransform {
* @see #createGeodeticTransformation(MathTransformFactory, DatumShiftGrid)
*/
@SuppressWarnings( {"OverridableMethodCallDuringObjectConstruction", "fallthrough"})
- protected <T extends Quantity<T>> InterpolatedTransform(final DatumShiftGrid<T,T> grid)
- throws NoninvertibleMatrixException
- {
+ protected <T extends Quantity<T>> InterpolatedTransform(final DatumShiftGrid<T,T> grid) throws NoninvertibleMatrixException {
/*
* Create the contextual parameters using the descriptor of the provider that created the datum shift grid.
- * If that provider is unknown, default (for now) to NTv2. This default may change in any future SIS version.
*/
- super((grid instanceof DatumShiftGridFile<?,?>) ? ((DatumShiftGridFile<?,?>) grid).descriptor : NTv2.PARAMETERS, grid);
+ super(grid.getParameterDescriptors(), grid);
if (!grid.isCellValueRatio()) {
throw new IllegalArgumentException(Resources.format(
Resources.Keys.IllegalParameterValue_2, "isCellValueRatio", Boolean.FALSE));
@@ -181,26 +176,15 @@ public class InterpolatedTransform extends DatumShiftTransform {
/*
* Denormalization is the inverse of all above conversions in the usual case (NADCON and NTv2) where the
* source coordinate system is the same than the target coordinate system, for example with axis unit in
- * degrees. However we also use this InterpolatedTransform implementation for other operation, like the
+ * degrees. However we also use this InterpolatedTransform implementation for other operations, like the
* one created by LocalizationGridBuilder. Those later operations may require a different denormalization
- * matrix.
+ * matrix. Consequently the call to `getParameterValues(…)` may overwrite the denormalization matrix as
+ * a non-documented side effect.
*/
- Matrix denormalize = null;
- if (grid instanceof DatumShiftGridFile<?,?>) {
- denormalize = ((DatumShiftGridFile<?,?>) grid).gridToTarget();
- }
- if (denormalize == null) {
- denormalize = normalize.inverse(); // Normal NACDON and NTv2 case.
- }
+ Matrix denormalize = normalize.inverse(); // Normal NACDON and NTv2 case.
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(denormalize);
+ grid.getParameterValues(context); // May overwrite `denormalize` (see above comment).
inverse = createInverse();
- /*
- * Parameters completed last because some DatumShiftGridFile subclasses (e.g. ResidualGrid) needs the
- * (de)normalization matrices.
- */
- if (grid instanceof DatumShiftGridFile<?,?>) {
- ((DatumShiftGridFile<?,?>) grid).setGridParameters(context);
- }
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java
index 6386fc3..c5497f4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyFormula.java
@@ -47,7 +47,7 @@ import static java.lang.Math.*;
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.0
* @since 0.7
* @module
*/
@@ -367,7 +367,7 @@ abstract class MolodenskyFormula extends DatumShiftTransform {
/*
* Following is executed only in InterpolatedMolodenskyTransform case.
*/
- grid.interpolateInCell(grid.normalizedToGridX(λt), grid.normalizedToGridY(φt), offset);
+ grid.interpolateInCell(normalizedToGridX(λt), normalizedToGridY(φt), offset);
tX = -offset[0];
tY = -offset[1];
tZ = -offset[2];
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
index 9030ac0..a5ba367 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
@@ -61,7 +61,7 @@ public final strictfp class ResidualGridTest extends TestCase {
public void verifyGlobalProperties() {
assertEquals("translationDimensions", 2, grid.getTranslationDimensions());
assertTrue("coordinateToGrid.isIdentity", grid.getCoordinateToGrid().isIdentity());
- assertTrue("gridToTarget.isIdentity", grid.gridToTarget().isIdentity());
+ assertTrue("gridToTarget.isIdentity", grid.gridToTarget.isIdentity());
}
/**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/QuadraticShiftGrid.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/QuadraticShiftGrid.java
index b7ff5d4..6eb20df 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/QuadraticShiftGrid.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/QuadraticShiftGrid.java
@@ -16,10 +16,14 @@
*/
package org.apache.sis.referencing.operation.transform;
+import java.util.Collections;
import java.awt.geom.AffineTransform;
import javax.measure.quantity.Dimensionless;
+import org.opengis.parameter.ParameterDescriptorGroup;
import org.apache.sis.referencing.datum.DatumShiftGrid;
import org.apache.sis.measure.Units;
+import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
+import org.apache.sis.parameter.Parameters;
/**
@@ -147,4 +151,20 @@ final strictfp class QuadraticShiftGrid extends DatumShiftGrid<Dimensionless,Dim
public double getCellPrecision() {
return PRECISION;
}
+
+ /**
+ * Returns a dummy parameter descriptor for this test.
+ */
+ @Override
+ public ParameterDescriptorGroup getParameterDescriptors() {
+ return new DefaultParameterDescriptorGroup(
+ Collections.singletonMap(DefaultParameterDescriptorGroup.NAME_KEY, "Test grid"), 0, 1);
+ }
+
+ /**
+ * No parameter to set for this test.
+ */
+ @Override
+ public void getParameterValues(Parameters parameters) {
+ }
}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
index abee72c..ed5d200 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Strings.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.internal.util;
+import java.lang.reflect.Array;
import java.util.Formatter;
import java.util.FormattableFlags;
import org.apache.sis.util.Static;
@@ -226,7 +227,7 @@ public final class Strings extends Static {
* @return a string representation of an instance of the given class having the given properties.
*/
public static String toString(final Class<?> classe, final Object... properties) {
- final StringBuffer buffer = new StringBuffer(32).append(Classes.getShortName(classe)).append('[');
+ final StringBuilder buffer = new StringBuilder(32).append(Classes.getShortName(classe)).append('[');
boolean isNext = false;
for (int i=0; i<properties.length; i++) {
final Object value = properties[++i];
@@ -238,10 +239,17 @@ public final class Strings extends Static {
if (name != null) {
buffer.append(name).append('=');
}
- final boolean isText = (value instanceof CharSequence);
- if (isText) buffer.append('“');
- buffer.append(value);
- if (isText) buffer.append('”');
+ if (value.getClass().isArray()) {
+ final int n = Array.getLength(value);
+ if (n != 1) buffer.append('{');
+ for (int j=0; j<n; j++) {
+ if (j != 0) buffer.append(", ");
+ append(Array.get(value, j), buffer);
+ }
+ if (n != 1) buffer.append('}');
+ } else {
+ append(value, buffer);
+ }
isNext = true;
}
}
@@ -249,6 +257,16 @@ public final class Strings extends Static {
}
/**
+ * Appends the given value in the given buffer, using quotes if the value is a character sequence.
+ */
+ private static void append(final Object value, final StringBuilder buffer) {
+ final boolean isText = (value instanceof CharSequence);
+ if (isText) buffer.append('“');
+ buffer.append(value);
+ if (isText) buffer.append('”');
+ }
+
+ /**
* Formats the given character sequence to the given formatter. This method takes in account
* the {@link FormattableFlags#UPPERCASE} and {@link FormattableFlags#LEFT_JUSTIFY} flags.
*