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 33701c6 Make possible to apply linearizers on localization grid and
have the final coordinates in the "linearized" CRS, without converting them
back to the original CRS. The use case is the application of Universal
Transverse Mercator (UTM) projection on GCOM rasters for making their "grid to
CRS" closer to affine transforms. In this case, we want to keep UTM coordinates
in the final result, not reconverting them back to geographic coordinates.
33701c6 is described below
commit 33701c6bcacaadb984ad7f3b1d8629fc5b7ef8dd
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Feb 17 00:34:37 2021 +0100
Make possible to apply linearizers on localization grid and have the final
coordinates in the "linearized" CRS, without converting them back to the
original CRS.
The use case is the application of Universal Transverse Mercator (UTM)
projection on GCOM rasters for making their "grid to CRS" closer to affine
transforms.
In this case, we want to keep UTM coordinates in the final result, not
reconverting them back to geographic coordinates.
---
.../operation/builder/LinearTransformBuilder.java | 261 ++++++++++++++-------
.../operation/builder/LocalizationGridBuilder.java | 115 +++++++--
.../operation/builder/ProjectedTransformTry.java | 169 ++++++++++---
.../operation/builder/ResidualGrid.java | 6 +-
.../operation/transform/InterpolatedTransform.java | 28 ++-
.../builder/LinearTransformBuilderTest.java | 7 +-
.../operation/builder/ResidualGridTest.java | 2 +-
.../transform/InterpolatedTransformTest.java | 2 +-
.../apache/sis/internal/earth/netcdf/GCOM_C.java | 13 +-
.../apache/sis/internal/earth/netcdf/GCOM_W.java | 24 +-
.../java/org/apache/sis/internal/netcdf/Axis.java | 17 +-
.../org/apache/sis/internal/netcdf/CRSBuilder.java | 25 +-
.../org/apache/sis/internal/netcdf/Convention.java | 8 +
.../org/apache/sis/internal/netcdf/Decoder.java | 17 +-
.../java/org/apache/sis/internal/netcdf/Grid.java | 63 +++--
.../apache/sis/internal/netcdf/GridCacheKey.java | 29 ++-
.../apache/sis/internal/netcdf/GridCacheValue.java | 77 ++++++
.../org/apache/sis/internal/netcdf/Linearizer.java | 258 +++++++++++++-------
.../org/apache/sis/internal/netcdf/Resources.java | 5 +
.../sis/internal/netcdf/Resources.properties | 1 +
.../sis/internal/netcdf/Resources_fr.properties | 1 +
.../sis/internal/netcdf/SatelliteGroundTrack.java | 246 -------------------
.../internal/netcdf/SatelliteGroundTrackTest.java | 81 -------
.../org/apache/sis/test/suite/NetcdfTestSuite.java | 1 -
24 files changed, 828 insertions(+), 628 deletions(-)
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
index d5849ab..1f8a882 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java
@@ -22,7 +22,6 @@ import java.util.Queue;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.ArrayDeque;
-import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Locale;
@@ -54,6 +53,7 @@ import
org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
import org.apache.sis.internal.referencing.DirectPositionView;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.util.AbstractMap;
+import org.apache.sis.internal.util.Numerics;
import org.apache.sis.internal.util.Strings;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
@@ -88,7 +88,7 @@ import org.apache.sis.util.Classes;
* That selected projection is given by {@link #linearizer()}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @see LocalizationGridBuilder
* @see LinearTransform
@@ -185,7 +185,7 @@ public class LinearTransformBuilder extends
TransformBuilder {
* Calculated fields, namely {@link #correlations} and {@link #transform},
are left uninitialized.
* Arrays are copied by references and their content shall not be
modified. The new builder should
* not be made accessible to users since changes in this builder would be
reflected in the source
- * values or original builder. This constructor is reserved to {@link
#create(MathTransformFactory)}
+ * values of original builder. This constructor is reserved to {@link
#create(MathTransformFactory)}
* internal usage.
*
* @param original the builder from which to take array references of
source values.
@@ -486,8 +486,8 @@ search: for (int j=numPoints; --j >= 0;) {
/**
* Returns the envelope of target points (<em>values</em> of the map
returned by {@link #getControlPoints()}).
* The number of dimensions is equal to {@link #getTargetDimensions()}.
The lower and upper values are inclusive.
- * If a {@linkplain #linearizer() linearizer has been applied}, then
coordinates of
- * the returned envelope are projected by that linearizer.
+ * If a {@linkplain #linearizer() linearizer has been applied}, then
coordinates of the returned envelope
+ * are projected by that linearizer.
*
* @return the envelope of target points.
* @throws IllegalStateException if the target points are not yet known.
@@ -619,7 +619,7 @@ search: for (int j=numPoints; --j >= 0;) {
}
/*
* If the point contains some NaN or infinite coordinate values,
it is okay to leave it as-is
- * (without incrementing 'numPoints') provided that we ensure that
at least one value is NaN.
+ * (without incrementing `numPoints`) provided that we ensure that
at least one value is NaN.
* For convenience, we set only the first coordinate to NaN. The
ControlPoints map will check
* for the first coordinate too, so we need to keep this policy
consistent.
*/
@@ -700,7 +700,7 @@ search: for (int j=numPoints; --j >= 0;) {
if (data != null && coord.length == data.length) {
search: for (int j=domain(); --j >= 0;) {
for (int i=0; i<coord.length; i++) {
- if (coord[i] != data[i][j]) { //
Intentionally want 'false' for NaN values.
+ if (coord[i] != data[i][j]) { //
Intentionally want `false` for NaN values.
continue search;
}
}
@@ -1103,8 +1103,8 @@ search: for (int j=domain(); --j >= 0;) {
double minAfter = Double.POSITIVE_INFINITY;
double maxAfter = Double.NEGATIVE_INFINITY;
double previous = coordinates[0];
- for (int x=0; x<stride; x++) { // For
iterating over dimensions lower than 'dimension'.
- for (int y=0; y<gridLength; y += page) { // For
iterating over dimensions greater than 'dimension'.
+ for (int x=0; x<stride; x++) { // For
iterating over dimensions lower than `dimension`.
+ for (int y=0; y<gridLength; y += page) { // For
iterating over dimensions greater than `dimension`.
final int stop = y + page;
for (int i = x+y; i<stop; i += stride) {
double value = coordinates[i];
@@ -1188,50 +1188,92 @@ search: for (int j=domain(); --j >= 0;) {
}
/**
- * Adds transforms to potentially apply on target coordinates before to
compute the linear transform.
- * This method can be invoked if one suspects that the <cite>source to
target</cite> transform may be
- * more linear when the target is another space than the current space of
{@linkplain #getTargetEnvelope()
- * target coordinates}. If linearizers have been specified, then the
{@link #create(MathTransformFactory)}
- * method will try to apply each transform on target coordinates and check
which one results in the best
- * {@linkplain #correlation() correlation} coefficients. It may be none.
- *
- * <p>The linearizers are specified as {@link MathTransform}s from current
{@linkplain #getTargetEnvelope()
- * target coordinates} to other spaces where <cite>sources to new
targets</cite> transforms may be more linear.
- * Keys in the map are arbitrary identifiers used in {@link #toString()}
for debugging purpose.
- * Values in the map are non-{@link LinearTransform}s (linear transforms
are not forbidden, but are useless for this process).</p>
- *
- * <p>The {@code projToGrid} argument maps {@code projections} dimensions
to this builder target dimensions.
- * For example if {@code projToGrid} array is {@code {2,1}}, then
dimensions 0 and 1 of given {@code projections}
- * (both source and target dimensions) will map to dimensions 2 and 1 of
this builder target dimensions, respectively.
- * The {@code projToGrid} argument can be omitted or null, in which {0, 1,
2 … {@link #getTargetDimensions()} - 1} is assumed.
- * All given {@code projections} shall have a number of source and target
dimensions equals to the length of the given or assumed
- * {@code projToGrid} array. It is possible to invoke this method many
times with different {@code projToGrid} argument values.</p>
+ * Adds transforms to potentially apply on target control points before to
compute the linear transform.
+ * This method can be invoked when the <cite>source to target</cite>
transform would possibly be more
+ * linear if <cite>target</cite> was another space than the {@linkplain
#getTargetEnvelope() current one}.
+ * If linearizers have been specified, then the {@link
#create(MathTransformFactory)} method will try to
+ * apply each transform on target coordinates and check which one get the
best
+ * {@linkplain #correlation() correlation} coefficients.
+ *
+ * <p>Exactly one of the specified transforms will be selected. If
applying no transform is an acceptable solution,
+ * then an {@linkplain
org.apache.sis.referencing.operation.transform.MathTransforms#identity(int)
identity transform}
+ * should be included in the given {@code projections} map. The transform
selected by {@code LinearTransformBuilder}
+ * will be given by {@link #linearizer()}.</p>
+ *
+ * <p>Linearizers are specified as a collection of {@link MathTransform}s
from current {@linkplain #getTargetEnvelope()
+ * target coordinates} to some other spaces where <cite>sources to new
targets</cite> transforms may be more linear.
+ * Keys in the map are arbitrary identifiers.
+ * Values in the map should be non-linear transforms; {@link
LinearTransform}s (other than identity)
+ * should be avoided because they will consume processing power for no
correlation improvement.</p>
+ *
+ * <h4>Error handling</h4>
+ * If a {@link org.opengis.referencing.operation.TransformException}
occurred or if some transform results
+ * were NaN or infinite, then the {@link MathTransform} that failed will
be ignored. If all transforms fail,
+ * then a {@link FactoryException} will be thrown by the {@code create(…)}
method.
+ *
+ * <h4>Dimensions mapping</h4>
+ * The {@code projToGrid} argument maps {@code projections} dimensions to
target dimensions of this builder.
+ * For example if {@code projToGrid} array is {@code {2,1}}, then
coordinate values in target dimensions 2 and 1
+ * of this grid will be used as source coordinates in dimensions 0 and 1
respectively for all given projections.
+ * Likewise, the projection results in dimensions 0 and 1 of all
projections will be stored in target dimensions
+ * 2 and 1 respectively of this grid.
+ *
+ * <p>The {@code projToGrid} argument can be omitted or null, in which
case {0, 1, 2 …
+ * {@link #getTargetDimensions()} - 1} is assumed. All given {@code
projections} shall have
+ * a number of source and target dimensions equals to the length of the
{@code projToGrid} array.
+ * It is possible to invoke this method many times with different {@code
projToGrid} argument values.</p>
*
* @param projections projections from current target coordinates to
other spaces which may result in more linear transforms.
- * @param projToGrid the target dimensions to project, or null or
omitted for projecting all target dimensions.
+ * @param projToGrid the target dimensions to project, or null or
omitted for projecting all target dimensions in same order.
* @throws IllegalStateException if {@link #create(MathTransformFactory)
create(…)} has already been invoked.
+ * @throws MismatchedDimensionException if a projection does not have the
expected number of dimensions.
*
* @see #linearizer()
* @see #correlation()
*
* @since 1.0
*/
- public void addLinearizers(final Map<String,MathTransform> projections,
int... projToGrid) {
+ public void addLinearizers(final Map<String,MathTransform> projections,
final int... projToGrid) {
+ ArgumentChecks.ensureNonNull("projections", projections);
+ addLinearizers(projections, false, projToGrid);
+ }
+
+ /**
+ * Implementation of {@link #addLinearizers(Map, int...)} with a flag
telling whether the inverse of selected projection
+ * shall be concatenated to the final transform. This method is non-public
because the {@code reverseAfterLinearization}
+ * flag has no purpose for this {@link LinearTransformBuilder} class; it
is useful only for {@link LocalizationGridBuilder}.
+ *
+ * @see ProjectedTransformTry#reverseAfterLinearization
+ */
+ final void addLinearizers(final Map<String,MathTransform> projections,
final boolean compensate, int[] projToGrid) {
ensureModifiable();
final int tgtDim = getTargetDimensions();
if (projToGrid == null || projToGrid.length == 0) {
projToGrid = ArraysExt.range(0, tgtDim);
+ } else {
+ long defined = 0;
+ projToGrid = projToGrid.clone();
+ for (final int d : projToGrid) {
+ ArgumentChecks.ensureValidIndex(tgtDim, d);
+ if (defined == (defined |= Numerics.bitmask(d))) {
+ // Note: if d ≥ 64, there will be no check (mask = 0).
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.DuplicatedNumber_1, d));
+ }
+ }
}
if (linearizers == null) {
- linearizers = new ArrayList<>();
+ linearizers = new ArrayList<>(projections.size());
}
for (final Map.Entry<String,MathTransform> entry :
projections.entrySet()) {
- linearizers.add(new ProjectedTransformTry(entry.getKey(),
entry.getValue(), projToGrid, tgtDim));
+ linearizers.add(new ProjectedTransformTry(entry.getKey(),
entry.getValue(), projToGrid, tgtDim, compensate));
}
}
/**
* Sets the linearizers to a copy of those of the given builder.
+ * This is used by copy constructors.
+ *
+ * @see
LocalizationGridBuilder#LocalizationGridBuilder(LinearTransformBuilder)
*/
final void setLinearizers(final LinearTransformBuilder other) {
if (other.linearizers != null) {
@@ -1242,8 +1284,8 @@ search: for (int j=domain(); --j >= 0;) {
/**
* Creates a linear transform approximation from the source positions to
the target positions.
- * This method assumes that source positions are precise and that all
uncertainty is in the target positions.
- * If {@linkplain #addLinearizers linearizers have been specified}, then
this method may project all target
+ * This method assumes that source positions are precise and that all
uncertainties are in target positions.
+ * If {@linkplain #addLinearizers linearizers have been specified}, then
this method will project all target
* coordinates using one of those linearizers in order to get a more
linear transform.
* If such projection is applied, then {@link #linearizer()} will return a
non-empty value after this method call.
*
@@ -1261,61 +1303,102 @@ search: for (int j=domain(); --j >= 0;) {
@Override
public LinearTransform create(final MathTransformFactory factory) throws
FactoryException {
if (transform == null) {
- MatrixSIS matrix = fit();
- if (linearizers != null) {
+ MatrixSIS bestTransform;
+ if (linearizers == null || linearizers.isEmpty()) {
+ bestTransform = fit();
+ } else {
/*
- * We are going to try to project target coordinates in an
attempt to find a more linear transform.
- * If a projection allows better results than unprojected
coordinates, the following variables will
- * be set to values to assign to this 'LinearTransformBuilder'
after the loop. We do not assign new
- * values to this 'LinearTransformBuilder' directly (as we
find them) in the loop because the checks
- * for a better transform require the original values.
+ * We are going to try to project target coordinates in search
for the most linear transform.
+ * If a projection allows better results, the following
variables will be set to values to assign
+ * to this `LinearTransformBuilder` after the loop. We do not
assign new values to this builder
+ * directly (as we find them) in the loop because the search
for a better transform requires the
+ * original values.
*/
- final double sqrtLength = Math.sqrt(correlations.length);
- double bestCorrelation = rms(correlations, sqrtLength);
+ bestTransform = null;
+ double bestCorrelation = 0;
double[] bestCorrelations = null;
- MatrixSIS bestTransform = null;
double[][] transformedArrays = null;
+ final double sqrtCorrLength = Math.sqrt(targets.length); //
For `bestCorrelation` calculation.
/*
- * Store the correlation when using no conversions, only for
this.toString() purpose. We copy
- * 'ProjectedTransformTry' list in an array both for excluding
the dummy entry, and also for
- * avoiding ConcurrentModificationException if a debugger
invokes toString() during the loop.
+ * If one of the transforms is identity, we can do the
computation directly on `this` because the
+ * `targets` arrays do not need to be transformed. This
special case avoids the need to allocate
+ * arrays from the `pool` and to copy data.
*/
- final ProjectedTransformTry[] alternatives =
linearizers.toArray(new ProjectedTransformTry[linearizers.size()]);
- linearizers.add(new ProjectedTransformTry((float)
bestCorrelation));
+ ProjectedTransformTry identity = null;
+ for (final ProjectedTransformTry alt : linearizers) {
+ if (alt.projection.isIdentity()) {
+ bestTransform = fit();
+ bestCorrelations = correlations;
+ bestCorrelation = rms(bestCorrelations,
sqrtCorrLength);
+ transformedArrays = targets;
+ appliedLinearizer = alt;
+ identity = alt;
+ alt.correlation = (float) bestCorrelation;
+ break;
+ }
+ }
/*
- * 'tmp' and 'pool' are temporary objects for this computation
only. We use a pool because the
- * 'double[]' arrays may be large (e.g. megabytes) and we want
to avoid creating new arrays of
+ * `tmp` and `pool` are temporary objects for this computation
only. We use a pool because the
+ * `double[]` arrays may be large (e.g. megabytes) and we want
to avoid creating new arrays of
* such size for each projection to try.
*/
final Queue<double[]> pool = new ArrayDeque<>();
- final int n = (gridLength != 0) ? gridLength : numPoints;
final LinearTransformBuilder tmp = new
LinearTransformBuilder(this);
- for (final ProjectedTransformTry alt : alternatives) {
- if ((tmp.targets = alt.transform(targets, n, pool)) !=
null) {
+ final int numPoints = (gridLength != 0) ? gridLength :
this.numPoints;
+ boolean needTargetReplace = false;
+ for (final ProjectedTransformTry alt : linearizers) {
+ if (alt == identity || (tmp.targets =
alt.transform(targets, numPoints, pool)) == null) {
+ continue;
+ }
+ /*
+ * At this point, a transformation has been successfully
applied on the target arrays of `tmp`.
+ * If we never invoked `fit()` before, its first call must
be done with all dimensions in `tmp`,
+ * not only the dimensions on which we apply the
`MathTransform`.
+ */
+ if (bestTransform == null) {
+ transformedArrays = tmp.targets =
alt.replaceTransformed(targets, tmp.targets);
+ bestTransform = tmp.fit();
+ bestCorrelations = tmp.correlations;
+ bestCorrelation = rms(bestCorrelations,
sqrtCorrLength);
+ alt.correlation = (float) bestCorrelation;
+ appliedLinearizer = alt;
+ } else {
+ /*
+ * For all invocations of `fit()` after the first one
(including the identity case if any),
+ * we need to do calculation only on the dimensions on
which `MathTransform` operates because
+ * calculation on other dimensions will be unchanged.
+ */
final MatrixSIS altTransform = tmp.fit();
- final double[] altCorrelations =
alt.replace(correlations, tmp.correlations);
- final double altCorrelation = rms(altCorrelations,
sqrtLength);
+ final double[] altCorrelations =
alt.replaceTransformed(bestCorrelations, tmp.correlations);
+ final double altCorrelation = rms(altCorrelations,
sqrtCorrLength);
alt.correlation = (float) altCorrelation;
if (altCorrelation > bestCorrelation) {
ProjectedTransformTry.recycle(transformedArrays,
pool);
transformedArrays = tmp.targets;
bestCorrelation = altCorrelation;
bestCorrelations = altCorrelations;
- bestTransform = alt.replace(matrix,
altTransform);
+ bestTransform =
alt.replaceTransformed(bestTransform, altTransform);
appliedLinearizer = alt;
+ needTargetReplace = true;
} else {
ProjectedTransformTry.recycle(tmp.targets, pool);
}
}
}
- if (bestTransform != null) {
- matrix = bestTransform;
- targets = transformedArrays;
- correlations = bestCorrelations;
+ /*
+ * Finished to try all transforms. If all of them failed, wrap
the `TransformException`.
+ */
+ if (bestTransform == null) {
+ throw new
FactoryException(ProjectedTransformTry.getError(linearizers));
+ }
+ if (needTargetReplace) {
+ transformedArrays =
appliedLinearizer.replaceTransformed(targets, transformedArrays);
}
+ targets = transformedArrays;
+ correlations = bestCorrelations;
}
// Set only on success.
- transform = (LinearTransform)
nonNull(factory).createAffineTransform(matrix);
+ transform = (LinearTransform)
nonNull(factory).createAffineTransform(bestTransform);
}
return transform;
}
@@ -1404,39 +1487,48 @@ search: for (int j=domain(); --j >= 0;) {
/**
* Returns a global estimation of correlation by computing the root mean
square of values.
*/
- private static double rms(final double[] correlations, final double
sqrtLength) {
- return org.apache.sis.math.MathFunctions.magnitude(correlations) /
sqrtLength;
+ private static double rms(final double[] correlations, final double
sqrtCorrLength) {
+ return org.apache.sis.math.MathFunctions.magnitude(correlations) /
sqrtCorrLength;
}
/**
* If target coordinates have been projected to another space, returns
that projection.
- * This method returns a non-empty value only if all the following
conditions are met:
- *
- * <ol>
- * <li>{@link #addLinearizers(Map, int...)} has been invoked.</li>
- * <li>{@link #create(MathTransformFactory)} has been invoked.</li>
- * <li>The {@code create(…)} method at step 2 found that projecting
target coordinates using
- * one of the linearizers specified at step 1 results in a more
linear transform.</li>
- * </ol>
- *
- * If this method returns a non-empty value, then the envelope returned by
{@link #getTargetEnvelope()}
- * and all control points returned by {@link #getControlPoint(int[])} are
projected by this transform.
- * The returned transform includes axes swapping specified by the {@code
dimensions} argument given to
- * <code>{@linkplain #addLinearizers(Map, int...) addLinearizers}(…,
dimensions)</code>.
+ * This method returns a non-empty value if {@link #addLinearizers(Map,
int...)} has been
+ * invoked with a non-empty map, followed by a {@link
#create(MathTransformFactory)} call.
+ * In such case, {@code LinearTransformBuilder} selects a linearizer
identified by the returned
+ * <var>key</var> - <var>value</var> entry. The entry key is one of the
keys of the maps given
+ * to {@code addLinearizers(…)}. The entry value is the associated {@code
MathTransform},
+ * possibly modified as described in the <cite>axis order</cite> section
below.
+ *
+ * <p>The envelope returned by {@link #getTargetEnvelope()} and all
control points
+ * returned by {@link #getControlPoint(int[])} are projected by the
selected transform.
+ * Consequently if the target coordinates of original control points are
desired,
+ * then the transform returned by {@code create(…)} needs to be
concatenated with
+ * the {@linkplain MathTransform#inverse() inverse} of the transform
returned by
+ * this {@code linearizer()} method.</p>
+ *
+ * <h4>Axis order</h4>
+ * The source coordinates expected by the returned transform are the
{@linkplain #getControlPoint(int[])
+ * control points target coordinates}. The returned transform will contain
an operation step performing
+ * axis filtering and swapping implied by the {@code projToGrid} argument
that was given to the
+ * <code>{@linkplain #addLinearizers(Map, int...) addLinearizers}(…,
projToGrid)}</code> method.
+ * Consequently if the {@code projToGrid} argument was not an arithmetic
progression,
+ * then the transform returned by this method will not be one of the
instances given to
+ * {@code addLinearizers(…)}.
*
* @return the projection applied on target coordinates before to compute
a linear transform.
*
- * @since 1.0
+ * @since 1.1
*/
- public Optional<MathTransform> linearizer() {
- return (appliedLinearizer != null) ?
Optional.of(appliedLinearizer.projection()) : Optional.empty();
+ public Optional<Map.Entry<String,MathTransform>> linearizer() {
+ return Optional.ofNullable(appliedLinearizer);
}
/**
- * Returns the identifier of the linearizer, or {@code null} if none.
+ * Returns linearizer which has been applied, or {@code null} if none.
*/
- final String linearizerID() {
- return (appliedLinearizer != null) ? appliedLinearizer.name() : null;
+ final ProjectedTransformTry appliedLinearizer() {
+ return appliedLinearizer;
}
/**
@@ -1513,17 +1605,18 @@ search: for (int j=domain(); --j >= 0;) {
* └────────────┴─────────────┘
*/
if (linearizers != null) {
+ final ProjectedTransformTry[] alternatives =
linearizers.toArray(new ProjectedTransformTry[linearizers.size()]);
+ Arrays.sort(alternatives);
buffer.append(Strings.CONTINUATION_ITEM);
vocabulary.appendLabel(Vocabulary.Keys.Preprocessing, buffer);
buffer.append(lineSeparator);
- Collections.sort(linearizers);
NumberFormat nf = null;
final TableAppender table = new TableAppender(buffer, " │ ");
table.appendHorizontalSeparator();
table.append(vocabulary.getString(Vocabulary.Keys.Conversion)).nextColumn();
table.append(vocabulary.getString(Vocabulary.Keys.Correlation)).nextLine();
table.appendHorizontalSeparator();
- for (final ProjectedTransformTry alt : linearizers) {
+ for (final ProjectedTransformTry alt : alternatives) {
nf = alt.summarize(table, nf, locale);
}
table.appendHorizontalSeparator();
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
index 41e228d..fca7409 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java
@@ -206,6 +206,9 @@ public class LocalizationGridBuilder extends
TransformBuilder {
* after construction will not be reflected in this new builder.</li>
* </ul>
*
+ * The new builder inherits the {@linkplain
LinearTransformBuilder#addLinearizers linearizers}
+ * of {@code localizations}.
+ *
* @param localizations the provider of control points for which to
create a localization grid.
* @throws ArithmeticException if this constructor can not infer a
reasonable grid size from the given localizations.
*
@@ -267,7 +270,7 @@ public class LocalizationGridBuilder extends
TransformBuilder {
double v = source.doubleValue(i) - min;
if (Math.abs(v % inc) > EPS) {
do {
- final double r = (inc % v); // Both 'inc' and 'v'
are positive, so 'r' will be positive too.
+ final double r = (inc % v); // Both `inc` and `v`
are positive, so `r` will be positive too.
inc = v;
v = r;
} while (Math.abs(v) > EPS);
@@ -283,7 +286,7 @@ public class LocalizationGridBuilder extends
TransformBuilder {
fromGrid.setElement(dim, dim, inc);
fromGrid.setElement(dim, SOURCE_DIMENSION, min);
final double n = span / inc;
- if (n >= 0.5 && n < source.size() - 0.5) { // Compare as
'double' in case the value is large.
+ if (n >= 0.5 && n < source.size() - 0.5) { // Compare as
`double` in case the value is large.
return ((int) Math.round(n)) + 1;
}
throw new
ArithmeticException(Resources.format(Resources.Keys.CanNotInferGridSizeFromValues_1,
range));
@@ -582,33 +585,59 @@ public class LocalizationGridBuilder extends
TransformBuilder {
}
/**
- * Adds transforms to potentially apply on target coordinates before to
compute the transform.
+ * Adds transforms to potentially apply on target control points before to
compute the transform.
* This method can be invoked if the departure from a linear transform is
too large, resulting
* in {@link InterpolatedTransform} to fail with "no convergence error"
messages.
* If linearizers have been specified, then the {@link
#create(MathTransformFactory)} method
* will try to apply each transform on target coordinates and check which
one results in the
- * best correlation coefficients. It may be none.
- *
- * <p>The linearizers are specified as {@link MathTransform}s from current
target coordinates
- * to other spaces where <cite>sources to new targets</cite> transforms
may be more linear.
- * The keys in the map are arbitrary identifiers used in {@link
#toString()} for debugging purpose.
- * The {@code dimensions} argument specifies which target dimensions to
project and can be null or omitted
- * if the projections shall be applied on all target coordinates. It is
possible to invoke this method many
- * times with different {@code dimensions} argument values.</p>
+ * best correlation coefficients. Exactly one of the specified transforms
will be selected.
+ * If applying no transform is an acceptable solution, then an
+ * {@linkplain
org.apache.sis.referencing.operation.transform.MathTransforms#identity(int)
+ * identity transform} should be included in the given {@code projections}
map.
+ *
+ * <p>The linearizers are specified as {@link MathTransform}s from current
{@linkplain #getControlPoint(int, int)
+ * target coordinates of control points} to other spaces where
<cite>sources to new targets</cite> transforms may
+ * be more linear. The keys in the map are arbitrary identifiers.
+ * The {@code projToGrid} argument specifies which control point
dimensions to use as {@code projections} source
+ * coordinates and can be null or omitted if the projections shall be
applied on all target coordinates.
+ * It is possible to invoke this method many times with different {@code
dimensions} argument values.</p>
+ *
+ * <p>The {@code compensate} argument tell whether the inverse of
specified transform shall be concatenated
+ * to the final {@linkplain #create interpolated transform}. If {@code
true}, the {@code projection} effect
+ * will be cancelled in the final result, i.e. the target coordinates will
be approximately the same as if
+ * no projection were applied. In such case, the advantage of applying a
projection is to improve numerical
+ * stability with a better linear approximation in used by the coordinate
transformation process.</p>
*
* @param projections projections from current target coordinates to
other spaces which may result in more linear transforms.
- * @param dimensions the target dimensions to project, or null or
omitted for projecting all target dimensions.
+ * @param compensate whether the inverse of selected projection shall
be concatenated to the final interpolated transform.
+ * @param projToGrid the target dimensions to project, or null or
omitted for projecting all target dimensions.
* If non-null and non-empty, then all transforms in
the {@code projections} map shall have a
* number of source and target dimensions equals to
the length of this array.
* @throws IllegalStateException if {@link #create(MathTransformFactory)
create(…)} has already been invoked.
*
* @see LinearTransformBuilder#addLinearizers(Map, int...)
*
- * @since 1.0
+ * @since 1.1
*/
- public void addLinearizers(final Map<String,MathTransform> projections,
int... dimensions) {
+ public void addLinearizers(final Map<String,MathTransform> projections,
final boolean compensate, final int... projToGrid) {
+ ArgumentChecks.ensureNonNull("projections", projections);
ensureModifiable();
- linear.addLinearizers(projections, dimensions);
+ linear.addLinearizers(projections, compensate, projToGrid);
+ }
+
+ /**
+ * Adds transforms to potentially apply on target control points before to
compute the transform.
+ *
+ * @param projections projections from current target coordinates to
other spaces which may result in more linear transforms.
+ * @param projToGrid the target dimensions to project, or null or
omitted for projecting all target dimensions.
+ *
+ * @deprecated Replaced by {@link #addLinearizers(Map, boolean, int...)}
with {@code compensate = true}.
+ *
+ * @since 1.0
+ */
+ @Deprecated
+ public void addLinearizers(final Map<String,MathTransform> projections,
final int... projToGrid) {
+ addLinearizers(projections, true, projToGrid);
}
/**
@@ -695,7 +724,8 @@ public class LocalizationGridBuilder extends
TransformBuilder {
} else {
step =
InterpolatedTransform.createGeodeticTransformation(nonNull(factory),
new ResidualGrid(sourceToGrid, gridToCoord,
width, height, residual,
- (gridPrecision > 0) ? gridPrecision :
DEFAULT_PRECISION, periods));
+ (gridPrecision > 0) ? gridPrecision :
DEFAULT_PRECISION, periods,
+ linear.appliedLinearizer()));
}
} catch (TransformException e) {
throw new FactoryException(e);
// Should never happen.
@@ -706,12 +736,12 @@ public class LocalizationGridBuilder extends
TransformBuilder {
* If those target coordinates have been modified in order to make
that step more
* linear, apply the inverse transformation after the step.
*/
- final Optional<MathTransform> linearizer = linear.linearizer();
- if (linearizer.isPresent()) try {
- step = factory.createConcatenatedTransform(step,
linearizer.get().inverse());
+ final ProjectedTransformTry linearizer =
linear.appliedLinearizer();
+ if (linearizer != null && linearizer.reverseAfterLinearization)
try {
+ step = factory.createConcatenatedTransform(step,
linearizer.getValue().inverse());
} catch (NoninvertibleTransformException e) {
throw new InvalidGeodeticParameterException(Resources.format(
- Resources.Keys.NonInvertibleOperation_1,
linear.linearizerID()), e);
+ Resources.Keys.NonInvertibleOperation_1,
linearizer.getKey()), e);
}
transform = step; // Set only after
everything succeeded.
}
@@ -719,6 +749,45 @@ public class LocalizationGridBuilder extends
TransformBuilder {
}
/**
+ * Returns the linearizer applied on target control points.
+ * This method returns a non-empty value if {@link #addLinearizers(Map,
boolean, int...)} has
+ * been invoked with a non-empty map, followed by a {@link
#create(MathTransformFactory)} call.
+ * In such case, {@link LinearTransformBuilder} selects a linearizer
identified by the returned
+ * <var>key</var> - <var>value</var> entry. The entry key is one of the
keys of the maps given
+ * to {@code addLinearizers(…)}. The entry value is the associated {@code
MathTransform},
+ * possibly modified as described in the <cite>axis order</cite> section
below.
+ *
+ * <p>All control points returned by {@link #getControlPoint(int, int)}
are projected by the selected transform.
+ * Consequently if the target coordinates of original control points are
desired, then the transform computed by
+ * this builder needs to be concatenated with the {@linkplain
MathTransform#inverse() inverse} of the transform
+ * returned by this method. This is done automatically in the {@link
#create(MathTransformFactory) create(…)}
+ * method if the {@code compensate} flag given to {@code
addLinearizers(…)} method was {@code true}.
+ * Otherwise the compensation, if desired, needs to be done by the
caller.</p>
+ *
+ * <h4>Axis order</h4>
+ * The returned transform will contain an operation step performing axis
filtering and swapping implied by the
+ * {@code projToGrid} argument that was given to the <code>{@linkplain
#addLinearizers(Map, boolean, int...)
+ * addLinearizers}(…, projToGrid)}</code> method. Consequently if the
{@code projToGrid} argument was not an
+ * arithmetic progression, then the transform returned by this method will
not be one of the instances given
+ * to {@code addLinearizers(…)}.
+ *
+ * @param ifNotCompensated whether to return the transform only if not
already compensated by {@code create(…)}.
+ * A value of {@code true} is useful if the caller wants the
transform only if it needs to compensate itself.
+ * @return the projection applied on target coordinates before to compute
a linear transform.
+ *
+ * @see LinearTransformBuilder#linearizer()
+ *
+ * @since 1.1
+ */
+ public Optional<Map.Entry<String,MathTransform>> linearizer(final boolean
ifNotCompensated) {
+ ProjectedTransformTry linearizer = linear.appliedLinearizer();
+ if (ifNotCompensated && linearizer != null &&
linearizer.reverseAfterLinearization) {
+ linearizer = null;
+ }
+ return Optional.ofNullable(linearizer);
+ }
+
+ /**
* Returns statistics of differences between values calculated by the
given transform and actual values.
* The given math transform is typically the transform computed by {@link
#create(MathTransformFactory)},
* but not necessarily. The returned statistics are:
@@ -765,9 +834,11 @@ public class LocalizationGridBuilder extends
TransformBuilder {
/*
* If a linearizer has been applied, all target coordinates in this
builder have been projected using
* that transform. We will need to apply the inverse transform in
order to get back the original values.
+ * The way that we get the transform below should be the same way than
in `create(…)`, except that we
+ * apply the inverse transform unconditionally.
*/
- final Optional<MathTransform> linearizer = linear.linearizer();
- final MathTransform complete = linearizer.isPresent() ?
linearizer.get().inverse() : null;
+ final ProjectedTransformTry linearizer = linear.appliedLinearizer();
+ final MathTransform complete = (linearizer != null) ?
linearizer.getValue().inverse() : null;
final MathTransform inverse = mt.inverse();
final int width = linear.gridSize(0);
final int height = linear.gridSize(1);
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
index ff43ea8..7d55c78 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
@@ -16,6 +16,8 @@
*/
package org.apache.sis.referencing.operation.builder;
+import java.util.Map;
+import java.util.List;
import java.util.Queue;
import java.util.Arrays;
import java.util.Locale;
@@ -29,7 +31,6 @@ import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Exceptions;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -54,12 +55,15 @@ import
org.apache.sis.referencing.operation.transform.MathTransforms;
* it will resolve the "no convergence" errors.</p>
* </div>
*
+ * <p><b>Note:</b> {@link #compareTo(ProjectedTransformTry)} is inconsistent
with {@link #equals(Object)}.
+ * The fact that {@link ProjectedTransformTry} instances are comparable should
not be visible in public API.</p>
+ *
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
-final class ProjectedTransformTry implements Comparable<ProjectedTransformTry>
{
+final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry>, Map.Entry<String,MathTransform> {
/**
* Number of points in the temporary buffer used for transforming data.
* The buffer length will be this capacity multiplied by the number of
dimensions.
@@ -69,70 +73,82 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
private static final int BUFFER_CAPACITY = 512;
/**
- * A name by witch this projection attempt is identified, or {@code null}
for the identity transform.
+ * A name by witch this projection attempt is identified.
+ *
+ * @see #getKey()
*/
- private String name;
+ private final String name;
/**
* A conversion from a non-linear grid (typically with longitude and
latitude values) to
* something that may be more linear (typically, but not necessarily, a
map projection).
*
- * @see #projection()
+ * @see #getValue()
*/
- private final MathTransform projection;
+ final MathTransform projection;
/**
* Maps {@link #projection} dimensions to {@link LinearTransformBuilder}
target dimensions.
* For example if this array is {@code {2,1}}, then dimensions 0 and 1 of
{@link #projection}
- * (both source and target dimensions) will map dimensions 2 and 1 of
{@link LinearTransformBuilder#targets}, respectively.
+ * (both source and target dimensions) will map dimensions 2 and 1 of
{@link LinearTransformBuilder#targets}.
* The length of this array shall be equal to the number of {@link
#projection} source dimensions.
*/
private final int[] projToGrid;
/**
+ * Whether the inverse of {@link #projection} shall be concatenated to the
+ * final {@linkplain LocalizationGridBuilder#create interpolated
transform}.
+ * If {@code true}, the {@code projection} effect will be cancelled in the
final result,
+ * i.e. the target coordinates will be approximately the same as if no
projection were applied.
+ * In such case, the advantage of applying a projection is to improve
numerical stability with
+ * a better linear approximation in the first step of the coordinate
transformation process.
+ */
+ final boolean reverseAfterLinearization;
+
+ /**
* A global correlation factor, stored for information purpose only.
*/
float correlation;
/**
* If an error occurred during coordinate operations, the error. Otherwise
{@code null}.
+ *
+ * @see #getError(List)
*/
private TransformException error;
/**
* Creates a new instance initialized to a copy of the given instance but
without result.
+ * This is used by copy constructors.
+ *
+ * @see
LocalizationGridBuilder#LocalizationGridBuilder(LinearTransformBuilder)
*/
ProjectedTransformTry(final ProjectedTransformTry other) {
name = other.name;
projection = other.projection;
projToGrid = other.projToGrid;
- }
-
- /**
- * Creates a new instance with only the given correlation coefficient.
This instance can not be used for
- * computation purpose. Its sole purpose is to hold the given coefficient
when no projection is applied.
- */
- ProjectedTransformTry(final float corr) {
- projection = null;
- projToGrid = null;
- correlation = corr;
+ reverseAfterLinearization = other.reverseAfterLinearization;
}
/**
* Prepares a new attempt to project a localization grid.
* All arguments are stored as-is (arrays are not cloned).
*
- * @param name a name by witch this projection attempt is
identified, or {@code null}.
+ * @param name a name by witch this projection attempt is
identified.
* @param projection conversion from non-linear grid to something
that may be more linear.
* @param projToGrid maps {@code projection} dimensions to {@link
LinearTransformBuilder} target dimensions.
* @param expectedDimension number of {@link LinearTransformBuilder}
target dimensions.
+ * @throws MismatchedDimensionException if the projection does not have
the expected number of dimensions.
*/
- ProjectedTransformTry(final String name, final MathTransform projection,
final int[] projToGrid, int expectedDimension) {
+ ProjectedTransformTry(final String name, final MathTransform projection,
final int[] projToGrid, int expectedDimension,
+ final boolean reverseAfterLinearization)
+ {
ArgumentChecks.ensureNonNull("name", name);
ArgumentChecks.ensureNonNull("projection", projection);
this.name = name;
this.projection = projection;
this.projToGrid = projToGrid;
+ this.reverseAfterLinearization = reverseAfterLinearization;
int side = 0; // 0 = problem with source
dimensions, 1 = problem with target dimensions.
int actual = projection.getSourceDimensions();
if (actual <= expectedDimension) {
@@ -150,22 +166,34 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
}
/**
- * Returns the name of this object, or {@code null} if this is the
identity transform created by
- * {@link #ProjectedTransformTry(float)}. Should never be {@code null} for
name returned to user.
+ * Returns the name by witch this projection attempt is identified.
*/
- final String name() {
+ @Override
+ public String getKey() {
return name;
}
/**
* Returns the projection, taking in account axis swapping if {@link
#projToGrid} is not an arithmetic progression.
+ *
+ * @todo We needs a pass-through transform if the number of grid dimensions
+ * is greater than the number of projection dimensions.
*/
- final MathTransform projection() {
+ @Override
+ public MathTransform getValue() {
MathTransform mt =
MathTransforms.linear(Matrices.createDimensionSelect(projToGrid.length,
projToGrid));
return MathTransforms.concatenate(mt, projection);
}
/**
+ * Do not allow modification of this entry.
+ */
+ @Override
+ public MathTransform setValue(MathTransform value) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Transforms target coordinates of a localization grid. The {@code
coordinates} argument is the value
* of {@link LinearTransformBuilder#targets}, without clone (this method
will only read those arrays).
* Only arrays at indices given by {@link #projToGrid} will be read; the
other arrays will be ignored.
@@ -178,6 +206,10 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
* <li>{@code results[d][i]} is a coordinate of the point at index
<var>i</var>.</li>
* </ol>
*
+ * The returned target shall be given to {@link
#replaceTransformed(double[][], double[][])} before
+ * final storage in {@link LinearTransformBuilder}.
+ *
+ * <h4>Pool of arrays</h4>
* The {@code pool} queue is initially empty. Arrays created by this
method and later discarded will be added to
* that queue, for recycling if this method is invoked again for another
{@code ProjectedTransformTry} instance.
*
@@ -233,7 +265,7 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
dataOffset = start;
int dst = d;
do {
- if (Double.isNaN(data[dataOffset] = buffer[dst])) {
+ if (!Double.isFinite(data[dataOffset] =
buffer[dst])) {
recycle(results, pool); // Make arrays
available for other transforms.
return null;
}
@@ -260,15 +292,43 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
}
/**
- * Replaces old correlation values by new values in a copy of the given
array.
+ * Returns {@code true} if {@code projToGrid[i] == i} for all <var>i</var>.
+ */
+ private boolean useSameDimensions() {
+ return ArraysExt.isRange(0, projToGrid);
+ }
+
+ /**
+ * Replaces old target arrays by new values in the dimensions where a
projection has been applied.
+ * The {@code targets} array is copied if necessary, then values are
replaced in the copied array.
+ * May return {@code newValues} directly if suitable.
+ *
+ * @param targets the original targets values. This array will not be
modified.
+ * @param newValues targets computed by {@link #transform transform(…)}
for the dimensions specified at construction time.
+ * @return a copy of the given {@code targets} array with new values
overwriting the old values.
+ */
+ final double[][] replaceTransformed(double[][] targets, final double[][]
newValues) {
+ if (newValues.length == targets.length && useSameDimensions()) {
+ return newValues;
+ }
+ targets = targets.clone();
+ for (int j=0; j<projToGrid.length; j++) {
+ targets[projToGrid[j]] = newValues[j];
+ }
+ return targets;
+ }
+
+ /**
+ * Replaces old correlation values by new values in the dimensions where a
projection has been applied.
+ * The {@code correlations} array is copied if necessary, then values are
replaced in the copied array.
* May return {@code newValues} directly if suitable.
*
* @param correlations the original correlation values. This array will
not be modified.
* @param newValues correlations computed by {@link
LinearTransformBuilder} for the dimensions specified at construction time.
* @return a copy of the given {@code correlation} array with new values
overwriting the old values.
*/
- final double[] replace(double[] correlations, final double[] newValues) {
- if (newValues.length == correlations.length && ArraysExt.isRange(0,
projToGrid)) {
+ final double[] replaceTransformed(double[] correlations, final double[]
newValues) {
+ if (newValues.length == correlations.length && useSameDimensions()) {
return newValues;
}
correlations = correlations.clone();
@@ -279,21 +339,22 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
}
/**
- * Replaces old transform coefficients by new values in a copy of the
given matrix.
+ * Replaces old transform coefficients by new values in the dimensions
where a projection has been applied.
+ * The {@code transform} matrix is copied if necessary, then values are
replaced in the copied matrix.
* May return {@code newValues} directly if suitable.
*
* @param transform the original affine transform. This matrix will not
be modified.
* @param newValues coefficients computed by {@link
LinearTransformBuilder} for the dimensions specified at construction time.
* @return a copy of the given {@code transform} matrix with new
coefficients overwriting the old values.
*/
- final MatrixSIS replace(MatrixSIS transform, final MatrixSIS newValues) {
+ final MatrixSIS replaceTransformed(MatrixSIS transform, final MatrixSIS
newValues) {
/*
* The two matrices shall have the same number of columns because they
were computed with
* LinearTransformBuilder instances having the same sources. However
the two matrices may
* have a different number of rows since the number of target
dimensions may differ.
*/
assert newValues.getNumCol() == transform.getNumCol();
- if (newValues.getNumRow() == transform.getNumRow() &&
ArraysExt.isRange(0, projToGrid)) {
+ if (newValues.getNumRow() == transform.getNumRow() &&
useSameDimensions()) {
return newValues;
}
transform = transform.clone();
@@ -307,8 +368,31 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
}
/**
+ * Returns the first error in the given list of linearizers. Errors after
the first one are added
+ * as suppressed exception. If no error are found, this method returns
{@code null}.
+ */
+ static TransformException getError(final List<ProjectedTransformTry>
linearizers) {
+ TransformException error = null;
+ for (final ProjectedTransformTry alt : linearizers) {
+ final TransformException e = alt.error;
+ if (e != null) {
+ if (error == null) {
+ error = e;
+ } else {
+ error.addSuppressed(e);
+ }
+ }
+ }
+ return error;
+ }
+
+ /**
* Orders by the inverse of correlation coefficients. Highest coefficients
(best correlations)
* are first, lower coefficients are next, {@link Float#NaN} values are
last.
+ *
+ * <p><b>Note:</b> this comparison is inconsistent with {@link
#equals(Object)}.
+ * The fact that {@link ProjectedTransformTry} instances are comparable
should
+ * not be visible in public API.</p>
*/
@Override
public int compareTo(final ProjectedTransformTry other) {
@@ -316,6 +400,26 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
}
/**
+ * Implements the {@link Map.Entry#equals(Object)} contract.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof Map.Entry<?,?>) {
+ final Map.Entry<?,?> other = (Map.Entry<?,?>) obj;
+ return name.equals(other.getKey()) &&
projection.equals(other.getValue());
+ }
+ return false;
+ }
+
+ /**
+ * Implements the {@link Map.Entry#hashCode()} contract.
+ */
+ @Override
+ public int hashCode() {
+ return name.hashCode() ^ projection.hashCode();
+ }
+
+ /**
* Formats a summary of this projection attempt. This method formats the
following columns:
*
* <ol>
@@ -329,9 +433,6 @@ final class ProjectedTransformTry implements
Comparable<ProjectedTransformTry> {
* @return format used for writing coefficients, or {@code null}.
*/
final NumberFormat summarize(final TableAppender table, NumberFormat nf,
final Locale locale) {
- if (name == null) {
- name =
Vocabulary.getResources(locale).getString(Vocabulary.Keys.Identity);
- }
table.append(name).nextColumn();
String message = "";
if (error != null) {
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 2b31c52..a3078f7 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
@@ -165,10 +165,12 @@ final class ResidualGrid extends
DatumShiftGrid<Dimensionless,Dimensionless> {
* @param residuals the residual data, as translations to apply on the
result of affine transform.
* @param precision desired precision of inverse transformations in
unit of grid cells.
* @param periods if grid coordinates in some dimensions are cyclic,
their periods in units of target CRS.
+ * @param linearizer the linearizer that have been applied, or {@code
null} if none.
*/
ResidualGrid(final LinearTransform sourceToGrid, final LinearTransform
gridToTarget,
final int nx, final int ny, final float[] residuals, final double
precision,
- final double[] periods) throws TransformException
+ final double[] periods, final ProjectedTransformTry linearizer)
+ throws TransformException
{
super(Units.UNITY, sourceToGrid, new int[] {nx, ny}, true,
Units.UNITY);
this.gridToTarget = gridToTarget;
@@ -176,7 +178,7 @@ final class ResidualGrid extends
DatumShiftGrid<Dimensionless,Dimensionless> {
this.accuracy = precision;
this.scanlineStride = nx;
double[] periodVector = null;
- if (periods != null && gridToTarget.isAffine()) {
+ if (periods != null && linearizer == null && gridToTarget.isAffine()) {
/*
* We require the transform to be affine because it makes the
Jacobian independent of
* coordinate values. It allows us to replace a period in target
CRS units by periods
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 8db3297..6e736fb 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
@@ -351,7 +351,7 @@ public class InterpolatedTransform extends
DatumShiftTransform {
* NOTE: we do not bother to override the methods expecting a 'float'
array because those methods should
* be rarely invoked. Since there is usually LinearTransforms before
and after this transform, the
* conversion between float and double will be handled by those
LinearTransforms. If nevertheless
- * this MolodenskyTransform is at the beginning or the end of a
transformation chain, the methods
+ * this InterpolatedTransform is at the beginning or the end of a
transformation chain, the methods
* inherited from the subclass will work (but may be slightly
slower).
*/
@@ -405,6 +405,24 @@ public class InterpolatedTransform extends
DatumShiftTransform {
* Transforms target coordinates to source coordinates. This is done by
iteratively finding target coordinates
* that shift to the input coordinates. The input coordinates is used as
the first approximation.
*
+ * <h2>Algorithm</h2>
+ * The algorithm used in this class takes some inspiration from the
+ * <a href="https://en.wikipedia.org/wiki/Gradient_descent">Gradient
descent</a> method, except that we do not use
+ * <em>gradient</em> direction. Instead we use <em>positional error</em>
direction computed with Jacobian matrix.
+ * Instead of moving in the opposite of gradient direction, we move in the
opposite of positional error vector.
+ * This algorithm works well when the errors are small, which is the case
for datum shift grids such as NADCON.
+ * It may work not so well with strongly curved <cite>localization
grids</cite> as found in some netCDF files.
+ * In such case, the iterative algorithm may throw a {@link
TransformException} with "No convergence" message.
+ * We could improve the algorithm by multiplying the translation vector by
some factor {@literal 0 < γ < 1},
+ * for example by taking inspiration from the Barzilai–Borwein method. But
this is not yet done for avoiding
+ * a computation cost penalty for the main target of this class, which is
datum shift grids.
+ *
+ * <h3>Possible improvement</h3>
+ * If strongly curved localization grids need to be supported, a possible
strategy for choosing a γ factor could
+ * be to compare the translation vector at an iteration step with the
translation vector at previous iteration.
+ * If the two vectors cancel each other, we can retry with γ=0.5. This is
assuming that the position we are
+ * looking for is located midway betweeb the two positions explored by the
two iteration steps.
+ *
* @author Rueben Schulz (UBC)
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.0
@@ -496,6 +514,7 @@ public class InterpolatedTransform extends
DatumShiftTransform {
/**
* Transforms an arbitrary amount of coordinate tuples.
+ * See class javadoc for some information about the algorithm.
*
* @throws TransformException if a point can not be transformed.
*/
@@ -586,6 +605,13 @@ public class InterpolatedTransform extends
DatumShiftTransform {
assert Math.abs(dx - d[0]) < tol &
Math.abs(dy - d[1]) < tol :
Arrays.toString(d) + " versus [" + dx + ", " + dy + "]";
}
+ /*
+ * At this point we got the gradient vector, which is
(dx,dy). In the simplest "gradient descent" method,
+ * we would just subtract this vector from (xi,yi). It
works well with datum shift grids (which are close
+ * to flat) but does not converge with some strongly
curved localization grids of remote sensors.
+ * A solution would be to multiply (dx,dy) by some γ
factor where 0 < γ < 1, but this is not done
+ * yet for avoiding the computation cost of
determining the γ value (see class javadoc).
+ */
xi -= dx;
yi -= dy;
if (!(Math.abs(ex) > tol || Math.abs(ey) > tol))
break; // Use '!' for catching NaN.
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
index 02fe30c..0a4ff0b 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilderTest.java
@@ -25,6 +25,7 @@ import org.opengis.util.FactoryException;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.operation.Matrix;
import org.apache.sis.referencing.operation.matrix.Matrix3;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.geometry.DirectPosition1D;
import org.apache.sis.geometry.DirectPosition2D;
import org.apache.sis.test.DependsOnMethod;
@@ -39,7 +40,7 @@ import static org.apache.sis.test.Assert.*;
* Tests {@link LinearTransformBuilder}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.5
* @module
*/
@@ -433,9 +434,11 @@ public final strictfp class LinearTransformBuilderTest
extends TestCase {
final NonLinearTransform tr = new NonLinearTransform();
builder.addLinearizers(Collections.singletonMap("x² y³", tr));
builder.addLinearizers(Collections.singletonMap("x³ y²", tr), 1, 0);
+ builder.addLinearizers(Collections.singletonMap("identity",
MathTransforms.identity(2)));
final Matrix m = builder.create(null).getMatrix();
- assertEquals("linearizer", "x³ y²", builder.linearizerID());
+ assertEquals("linearizer", "x³ y²",
builder.linearizer().get().getKey());
assertNotSame("linearizer", tr, builder.linearizer().get()); // Not
same because axes should have been swapped.
+ assertArrayEquals("correlations", new double[] {1, 1},
builder.correlation(), 1E-15);
assertMatrixEquals("linear",
new Matrix3(2, 0, 3,
0, 1, 1,
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 5cda046..b709276 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
@@ -53,7 +53,7 @@ public final strictfp class ResidualGridTest extends TestCase
{
0,2 , 1,2 , 2,1,
1,3 , 2,2 , 1,1,
0,4 , 2,3 , 3,2,
- 1,4 , 3,3 , 3,2}, 0.1, null);
+ 1,4 , 3,3 , 3,2}, 0.1, null, null);
}
/**
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedTransformTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedTransformTest.java
index e673b04..7dccfa9 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedTransformTest.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedTransformTest.java
@@ -41,7 +41,7 @@ import org.junit.Test;
* Tested transformations are:
*
* <ul>
- * <li>Simple case based on linear calculations (easier to debug).</li>
+ * <li>Simple case based on sinusoidal calculations (easier to debug).</li>
* <li>From NTF to RGF93 using a NTv2 grid.</li>
* <li>From NAD27 to NAD83 using a NADCON grid.</li>
* </ul>
diff --git
a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
index d66a56c..d4e1dd3 100644
---
a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
+++
b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
@@ -285,15 +285,18 @@ public final class GCOM_C extends Convention {
}
/**
- * Returns an enumeration of two-dimensional non-linear transforms that
may be tried in attempts to make
- * localization grid more linear.
+ * Returns the two-dimensional non-linear transforms to apply for making
the localization grid more linear.
+ * This method returns a singleton without specifying the identity
transform as an acceptable alternative.
+ * It means that the specified projection (UTM) is considered mandatory
for this format.
*
* @param decoder the netCDF file for which to determine linearizers
that may possibly apply.
- * @return enumeration of two-dimensional non-linear transforms to try.
+ * @return enumeration of two-dimensional non-linear transforms to apply.
+ *
+ * @see #defaultHorizontalCRS(boolean)
*/
@Override
public Set<Linearizer> linearizers(final Decoder decoder) {
- return Collections.singleton(Linearizer.GROUND_TRACK);
+ return Collections.singleton(new Linearizer(CommonCRS.WGS84,
Linearizer.Type.UTM));
}
/**
@@ -416,7 +419,7 @@ public final class GCOM_C extends Convention {
* Returns the default prime meridian, ellipsoid, datum or CRS to use if
no information is found in the netCDF file.
* GCOM documentation said that the datum is WGS 84.
*
- * @param spherical ignored, since we assume a sphere in all cases.
+ * @param spherical ignored, since we assume an ellipsoid in all cases.
* @return information about geodetic objects to use if no explicit
information is found in the file.
*/
@Override
diff --git
a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_W.java
b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_W.java
index b249a2e..3410a54 100644
---
a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_W.java
+++
b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_W.java
@@ -30,6 +30,7 @@ import org.apache.sis.internal.netcdf.Decoder;
import org.apache.sis.internal.netcdf.Variable;
import org.apache.sis.internal.netcdf.VariableRole;
import org.apache.sis.internal.netcdf.Linearizer;
+import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.operation.transform.TransferFunction;
@@ -179,15 +180,30 @@ public final class GCOM_W extends Convention {
/**
- * Returns an enumeration of two-dimensional non-linear transforms that
may be tried in attempts to make
- * localization grid more linear.
+ * Returns the two-dimensional non-linear transforms to apply for making
the localization grid more linear.
+ * This method returns a singleton without specifying the identity
transform as an acceptable alternative.
+ * It means that the specified projection (UTM) is considered mandatory
for this format.
*
* @param decoder the netCDF file for which to determine linearizers
that may possibly apply.
- * @return enumeration of two-dimensional non-linear transforms to try.
+ * @return enumeration of two-dimensional non-linear transforms to apply.
+ *
+ * @see #defaultHorizontalCRS(boolean)
*/
@Override
public Set<Linearizer> linearizers(final Decoder decoder) {
- return Collections.singleton(Linearizer.GROUND_TRACK);
+ return Collections.singleton(new Linearizer(CommonCRS.WGS84,
Linearizer.Type.UTM));
+ }
+
+ /**
+ * Returns the default prime meridian, ellipsoid, datum or CRS to use if
no information is found in the netCDF file.
+ * GCOM documentation said that the datum is WGS 84.
+ *
+ * @param spherical ignored, since we assume an ellipsoid in all cases.
+ * @return information about geodetic objects to use if no explicit
information is found in the file.
+ */
+ @Override
+ public CommonCRS defaultHorizontalCRS(final boolean spherical) {
+ return CommonCRS.WGS84;
}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index 5c1db79..65020c0 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -35,6 +35,7 @@ import org.opengis.referencing.cs.CoordinateSystemAxis;
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.opengis.metadata.content.TransferFunctionType;
import org.apache.sis.internal.referencing.AxisDirections;
import org.apache.sis.internal.util.Numerics;
@@ -380,7 +381,7 @@ public final class Axis extends NamedElement {
*
* @see #getMainSize()
*/
- final int getMainDirection() {
+ private int getMainDirection() {
return (getNumDimensions() < 2 || gridDimensionIndices[0] <=
gridDimensionIndices[1]) ? 0 : 1;
}
@@ -767,8 +768,11 @@ public final class Axis extends NamedElement {
* @return the localization grid, or {@code null} if none can be built.
* @throws IOException if an error occurred while reading the data.
* @throws DataStoreException if a logical error occurred.
+ * @throws TransformException if an unexpected error occurred during
application of a linearizer.
*/
- final MathTransform createLocalizationGrid(final Axis other) throws
IOException, FactoryException, DataStoreException {
+ final GridCacheValue createLocalizationGrid(final Axis other)
+ throws IOException, FactoryException, TransformException,
DataStoreException
+ {
if (getNumDimensions() != 2 || other.getNumDimensions() != 2) {
return null;
}
@@ -803,7 +807,7 @@ public final class Axis extends NamedElement {
*/
final Decoder decoder = coordinates.decoder;
final GridCacheKey keyLocal = new GridCacheKey(width, height, this,
other);
- MathTransform tr = keyLocal.cached(decoder);
+ GridCacheValue tr = keyLocal.cached(decoder);
if (tr != null) {
return tr;
}
@@ -816,7 +820,7 @@ public final class Axis extends NamedElement {
final Vector vy = other.read();
final Set<Linearizer> linearizers =
decoder.convention().linearizers(decoder);
final GridCacheKey.Global keyGlobal = new
GridCacheKey.Global(keyLocal, vx, vy, linearizers);
- final Cache.Handler<MathTransform> handler = keyGlobal.lock();
+ final Cache.Handler<GridCacheValue> handler = keyGlobal.lock();
try {
tr = handler.peek();
if (tr == null) {
@@ -845,7 +849,8 @@ public final class Axis extends NamedElement {
*/
final MathTransformFactory factory =
decoder.getMathTransformFactory();
if (!linearizers.isEmpty()) {
- Linearizer.applyTo(linearizers, factory, grid, this,
other);
+ // Current version does not need the factory, but future
version may use it.
+ Linearizer.setCandidatesOnGrid(new Axis[] {this, other},
linearizers, grid);
}
/*
* There is usually a one-to-one relationship between
localization grid cells and image pixels.
@@ -856,7 +861,7 @@ public final class Axis extends NamedElement {
* to take in account the case where dataToGridIndices()
returns 0.1.
*/
grid.setDesiredPrecision(0.001);
- tr = grid.create(factory);
+ tr = new GridCacheValue(linearizers, grid, factory);
tr = keyLocal.cache(decoder, tr);
}
} finally {
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index 782f947..e62c53e 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -41,6 +41,7 @@ import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.Matrix;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.cs.AbstractCS;
import org.apache.sis.referencing.cs.AxesConvention;
@@ -169,11 +170,21 @@ abstract class CRSBuilder<D extends Datum, CS extends
CoordinateSystem> {
/**
* Infers a new CRS for a {@link Grid}.
*
- * @param decoder the decoder of the netCDF from which the CRS are
constructed.
- * @param grid the grid for which the CRS are constructed.
+ * <h4>CRS replacements</h4>
+ * The {@code linearizationTargets} argument allows to replace some CRSs
inferred by this method by hard-coded CRSs.
+ * This is non-empty only when reading a netCDF file for a specific
profile, i.e. a file decoded with a subclass of
+ * {@link Convention}. The CRS to be replaced is inferred from the axis
directions.
+ *
+ * @param decoder the decoder of the netCDF from which the
CRS are constructed.
+ * @param grid the grid for which the CRS are
constructed.
+ * @param linearizationTargets CRS to use instead of CRS inferred by
this method, or null or empty if none.
+ * @param reorderGridToCRS an affine transform doing a final step in
a "grid to CRS" transform for ordering axes.
+ * Not used by this method, but may be modified for taking in
account axis order changes caused by replacements
+ * defined in {@code linearizationTargets}. Ignored (can be null)
if {@code linearizationTargets} is null.
* @return coordinate reference system from the given axes, or {@code
null}.
*/
- public static CoordinateReferenceSystem assemble(final Decoder decoder,
final Grid grid)
+ public static CoordinateReferenceSystem assemble(final Decoder decoder,
final Grid grid,
+ final List<CoordinateReferenceSystem> linearizationTargets, final
Matrix reorderGridToCRS)
throws DataStoreException, FactoryException, IOException
{
final List<CRSBuilder<?,?>> builders = new ArrayList<>(4);
@@ -184,6 +195,14 @@ abstract class CRSBuilder<D extends Datum, CS extends
CoordinateSystem> {
for (int i=0; i < components.length; i++) {
components[i] = builders.get(i).build(decoder, true);
}
+ /*
+ * If there is hard-coded CRS implied by `Convention.linearizers()`,
use it now.
+ * We do not verify the datum; we assume that the linearizer that
built the CRS
+ * was consistent with `Convention.defaultHorizontalCRS(false)`.
+ */
+ if ((linearizationTargets != null) && !linearizationTargets.isEmpty())
{
+ Linearizer.replaceInCompoundCRS(components, linearizationTargets,
reorderGridToCRS);
+ }
switch (components.length) {
case 0: return null;
case 1: return components[0];
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index 3ece160..012e366 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -356,10 +356,18 @@ public class Convention {
* one resulting in best {@linkplain org.apache.sis.math.Plane#fit linear
regression correlation coefficients}
* will be selected.
*
+ * <p>If the returned set is non-empty, exactly one of the linearizers
will be applied. If not applying any
+ * linearizer is an acceptable solution, then an identity linearizer
should be explicitly returned.</p>
+ *
+ * <p>The returned set shall not contain two linearizers of the same
{@linkplain Linearizer#type type}
+ * because the types (not the full linearizers) are used in keys for
caching localization grids.</p>
+ *
* <p>Default implementation returns an empty set.</p>
*
* @param decoder the netCDF file for which to get linearizer candidates.
* @return enumeration of two-dimensional non-linear transforms to try on
the localization grid.
+ *
+ * @see #defaultHorizontalCRS(boolean)
*/
public Set<Linearizer> linearizers(final Decoder decoder) {
return Collections.emptySet();
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 183569e..e5cfd4a 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -35,7 +35,6 @@ import org.opengis.util.NameSpace;
import org.opengis.util.NameFactory;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.event.StoreListeners;
@@ -130,16 +129,18 @@ public abstract class Decoder extends
ReferencingFactoryContainer implements Clo
/**
* Cache of localization grids created for a given pair of
(<var>x</var>,<var>y</var>) axes. Localization grids
- * are expensive to compute and consume a significant amount of memory.
The {@link Grid} instances returned by
- * {@link #getGrids()} allow to share localization grids only between
variables using the exact same list of dimensions.
+ * are expensive to compute and consume a significant amount of memory.
The {@link Grid} instances returned by
+ * {@link #getGrids()} share localization grids only between variables
using the exact same list of dimensions.
* This {@code localizationGrids} cache allows to cover other cases.
- * For example a netCDF file may have a variable with
(<var>longitude</var>, <var>latitude</var>) dimensions
- * and another variable with (<var>longitude</var>, <var>latitude</var>,
<var>depth</var>) dimensions,
- * with both variables using the same localization grid for the
(<var>longitude</var>, <var>latitude</var>) part.
+ *
+ * <div class="note"><b>Example:</b>
+ * a netCDF file may have a variable with (<var>longitude</var>,
<var>latitude</var>) dimensions and another
+ * variable with (<var>longitude</var>, <var>latitude</var>,
<var>depth</var>) dimensions, with both variables
+ * using the same localization grid for the (<var>longitude</var>,
<var>latitude</var>) part.</div>
*
* @see GridCacheKey#cached(Decoder)
*/
- final Map<GridCacheKey,MathTransform> localizationGrids;
+ final Map<GridCacheKey,GridCacheValue> localizationGrids;
/**
* Where to send the warnings.
@@ -437,7 +438,7 @@ public abstract class Decoder extends
ReferencingFactoryContainer implements Clo
if (list.isEmpty()) {
final List<Exception> warnings = new ArrayList<>(); // For
internal usage by Grid.
for (final Grid grid : getGrids()) {
- addIfNotPresent(list, grid.getCoordinateReferenceSystem(this,
warnings));
+ addIfNotPresent(list, grid.getCoordinateReferenceSystem(this,
warnings, null, null));
}
}
return list;
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
index f3f3d45..fe64f24 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
@@ -25,12 +25,11 @@ import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
-import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.metadata.spatial.DimensionNameType;
import org.apache.sis.internal.referencing.AxisDirections;
import org.apache.sis.referencing.operation.matrix.Matrices;
-import org.apache.sis.referencing.CRS;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.IllegalGridGeometryException;
@@ -48,7 +47,7 @@ import org.apache.sis.util.ArraysExt;
* if a variable dimensions should considered as bands instead than
spatiotemporal dimensions.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @see Decoder#getGrids()
*
@@ -79,7 +78,7 @@ public abstract class Grid extends NamedElement {
* The coordinate reference system, created when first needed.
* May be {@code null} even after we attempted to create it.
*
- * @see #getCoordinateReferenceSystem(Decoder, List)
+ * @see #getCoordinateReferenceSystem(Decoder, List, List, Matrix)
*/
private CoordinateReferenceSystem crs;
@@ -290,24 +289,34 @@ public abstract class Grid extends NamedElement {
* this method because this CRS will be used for adjusting axis order or
for completion if grid mapping does not include
* information for all dimensions.</p>
*
- * @param decoder the decoder for which CRS are constructed.
- * @param warnings previous warnings, for avoiding to log the same
message twice. Can be null.
- * @return the CRS for this grid geometry, or {@code null}.
- * @throws IOException if an I/O operation was necessary but failed.
- * @throws DataStoreException if the CRS can not be constructed.
+ * @param decoder the decoder for which CRS are constructed.
+ * @param warnings previous warnings, for avoiding to log
the same message twice. Can be null.
+ * @param linearizationTargets CRS to use instead of CRS inferred by
this method, or null or empty if none.
+ * @param reorderGridToCRS an affine transform doing a final step in
a "grid to CRS" transform for ordering axes.
+ * Not used by this method, but may be modified for taking in
account axis order changes caused by replacements
+ * defined in {@code linearizationTargets}. Ignored (can be null)
if {@code linearizationTargets} is null.
+ * @return the CRS for this grid geometry, or {@code null}.
+ * @throws IOException if an I/O operation was necessary but failed.
+ * @throws DataStoreException if the CRS can not be constructed.
*/
- final CoordinateReferenceSystem getCoordinateReferenceSystem(final Decoder
decoder, final List<Exception> warnings)
+ final CoordinateReferenceSystem getCoordinateReferenceSystem(final Decoder
decoder, final List<Exception> warnings,
+ final List<CoordinateReferenceSystem> linearizationTargets, final
Matrix reorderGridToCRS)
throws IOException, DataStoreException
{
- if (!isCRSDetermined) try {
- isCRSDetermined = true; // Set now for
avoiding new attempts if creation fail.
- crs = CRSBuilder.assemble(decoder, this);
+ final boolean isCached = (linearizationTargets == null) ||
linearizationTargets.isEmpty();
+ if (isCached & isCRSDetermined) {
+ return crs;
+ } else try {
+ if (isCached) isCRSDetermined = true; // Set now for
avoiding new attempts if creation fail.
+ final CoordinateReferenceSystem result =
CRSBuilder.assemble(decoder, this, linearizationTargets, reorderGridToCRS);
+ if (isCached) crs = result;
+ return result;
} catch (FactoryException | NullArgumentException ex) {
if (isNewWarning(ex, warnings)) {
canNotCreate(decoder, "getCoordinateReferenceSystem",
Resources.Keys.CanNotCreateCRS_3, ex);
}
+ return null;
}
- return crs;
}
/**
@@ -439,6 +448,7 @@ findFree: for (int srcDim :
axis.gridDimensionIndices) {
* two-dimensional localization grid. Those transforms require two
variables, i.e. "two-dimensional"
* axes come in pairs.
*/
+ final List<CoordinateReferenceSystem> linearizationTargets = new
ArrayList<>();
for (int i=0; i<nonLinears.size(); i++) { // Length of
'nonLinears' may change in this loop.
if (nonLinears.get(i) == null) {
for (int j=i; ++j < nonLinears.size();) {
@@ -460,13 +470,13 @@ findFree: for (int srcDim :
axis.gridDimensionIndices) {
case +1: ArraysExt.swap(gridAxes, 0, 1); break;
default: continue; // Needs axes at
consecutive source dimensions.
}
- final MathTransform grid =
gridAxes[0].createLocalizationGrid(gridAxes[1]);
+ final GridCacheValue grid =
gridAxes[0].createLocalizationGrid(gridAxes[1]);
if (grid != null) {
/*
* Replace the first transform by the
two-dimensional localization grid and
* remove the other transform. Removals need
to be done in arrays too.
*/
- nonLinears.set(i, grid);
+ nonLinears.set(i, grid.transform);
nonLinears.remove(j);
final int n = nonLinears.size() - j;
System.arraycopy(deferred, j+1,
deferred, j, n);
@@ -474,6 +484,7 @@ findFree: for (int srcDim :
axis.gridDimensionIndices) {
if (otherDim < srcDim) {
gridDimensionIndices[i] = otherDim; //
Index of the first dimension.
}
+
grid.getLinearizationTarget(linearizationTargets);
break; //
Continue the 'i' loop.
}
}
@@ -491,12 +502,19 @@ findFree: for (int srcDim :
axis.gridDimensionIndices) {
if (s < 0) return null;
}
/*
+ * Create the coordinate reference system now, because this method
may modify the `affine` transform.
+ * This modification happens only if `Convention.linearizers()`
specified transforms to apply on the
+ * localization grid for making it more linear. This is a
profile-dependent feature.
+ */
+ final CoordinateReferenceSystem crs =
getCoordinateReferenceSystem(decoder, null, linearizationTargets, affine);
+ /*
* Final transform, as the concatenation of the non-linear
transforms followed by the affine transform.
* We concatenate the affine transform last because it may change
axis order.
*/
MathTransform gridToCRS = null;
final int nonLinearCount = nonLinears.size();
final MathTransformFactory factory =
decoder.getMathTransformFactory();
+ // Not a non-linear transform, but we abuse this list for
convenience.
nonLinears.add(factory.createAffineTransform(affine));
for (int i=0; i <= nonLinearCount; i++) {
MathTransform tr = nonLinears.get(i);
@@ -514,17 +532,14 @@ findFree: for (int srcDim :
axis.gridDimensionIndices) {
* to be at the centers of the cells, but we do not require that
in this standard". We nevertheless check
* if an axis thinks otherwise.
*/
- final CoordinateReferenceSystem crs =
getCoordinateReferenceSystem(decoder, null);
- if (CRS.getHorizontalComponent(crs) instanceof GeographicCRS) {
- for (final Axis axis : axes) {
- if (axis.isCellCorner()) {
- anchor = PixelInCell.CELL_CORNER;
- break;
- }
+ for (final Axis axis : axes) {
+ if (axis.isCellCorner()) {
+ anchor = PixelInCell.CELL_CORNER;
+ break;
}
}
geometry = new GridGeometry(getExtent(axes), anchor, gridToCRS,
crs);
- } catch (FactoryException | IllegalGridGeometryException ex) {
+ } catch (FactoryException | TransformException |
IllegalGridGeometryException ex) {
canNotCreate(decoder, "getGridGeometry",
Resources.Keys.CanNotCreateGridGeometry_3, ex);
}
return geometry;
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
index c69df17..fae9e03 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheKey.java
@@ -18,9 +18,9 @@ package org.apache.sis.internal.netcdf;
import java.util.Set;
import java.util.Arrays;
+import java.util.EnumSet;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import org.opengis.referencing.operation.MathTransform;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.internal.util.Strings;
import org.apache.sis.internal.storage.io.ByteWriter;
@@ -39,7 +39,7 @@ import org.apache.sis.math.Vector;
* The base class if for local cache. The inner class is for the global cache.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
@@ -89,7 +89,7 @@ class GridCacheKey {
* Returns the localization grid from the local cache if one exists, or
{@code null} if none.
* This method looks only in the local cache. For the global cache, see
{@link Global#lock()}.
*/
- final MathTransform cached(final Decoder decoder) {
+ final GridCacheValue cached(final Decoder decoder) {
return decoder.localizationGrids.get(this);
}
@@ -101,8 +101,8 @@ class GridCacheKey {
* @param grid the grid to cache.
* @return the cached grid. Should be the given {@code grid} instance,
unless another grid has been cached concurrently.
*/
- final MathTransform cache(final Decoder decoder, final MathTransform grid)
{
- final MathTransform tr = decoder.localizationGrids.putIfAbsent(this,
grid);
+ final GridCacheValue cache(final Decoder decoder, final GridCacheValue
grid) {
+ final GridCacheValue tr = decoder.localizationGrids.putIfAbsent(this,
grid);
return (tr != null) ? tr : grid;
}
@@ -118,13 +118,13 @@ class GridCacheKey {
/**
* The global cache shared by all netCDF files. All grids are retained
by weak references.
*/
- private static final Cache<GridCacheKey,MathTransform> CACHE = new
Cache<>(12, 0, false);
+ private static final Cache<GridCacheKey,GridCacheValue> CACHE = new
Cache<>(12, 0, false);
/**
* The algorithms tried for making the localization grids more linear.
* May be empty but shall not be null.
*/
- private final Set<Linearizer> linearizers;
+ private final Set<Linearizer.Type> linearizerTypes;
/**
* Concatenation of the digests of the two vectors.
@@ -142,7 +142,10 @@ class GridCacheKey {
*/
Global(final GridCacheKey keyLocal, final Vector vx, final Vector vy,
final Set<Linearizer> linearizers) {
super(keyLocal);
- this.linearizers = linearizers;
+ linearizerTypes = EnumSet.noneOf(Linearizer.Type.class);
+ for (final Linearizer linearizer : linearizers) {
+ linearizerTypes.add(linearizer.type);
+ }
final MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
@@ -180,8 +183,8 @@ class GridCacheKey {
* This method must be used with a {@code try … finally} block as
below:
*
* {@preformat java
- * MathTransform tr;
- * final Cache.Handler<MathTransform> handler = key.lock();
+ * GridCacheValue tr;
+ * final Cache.Handler<GridCacheValue> handler = key.lock();
* try {
* tr = handler.peek();
* if (tr == null) {
@@ -192,7 +195,7 @@ class GridCacheKey {
* }
* }
*/
- final Cache.Handler<MathTransform> lock() {
+ final Cache.Handler<GridCacheValue> lock() {
return CACHE.lock(this);
}
@@ -201,7 +204,7 @@ class GridCacheKey {
* The hash code uses a digest of coordinate values given at
construction time.
*/
@Override public int hashCode() {
- return super.hashCode() + linearizers.hashCode() +
Arrays.hashCode(digest);
+ return super.hashCode() + linearizerTypes.hashCode() +
Arrays.hashCode(digest);
}
/**
@@ -213,7 +216,7 @@ class GridCacheKey {
@Override public boolean equals(final Object other) {
if (super.equals(other)) {
final Global that = (Global) other;
- if (linearizers.equals(that.linearizers)) {
+ if (linearizerTypes.equals(that.linearizerTypes)) {
return Arrays.equals(digest, that.digest);
}
}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
new file mode 100644
index 0000000..cd3be6d
--- /dev/null
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridCacheValue.java
@@ -0,0 +1,77 @@
+/*
+ * 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.netcdf;
+
+import java.util.Set;
+import java.util.List;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
+
+
+/**
+ * A value cached in {@link GridCacheKey.Global#CACHE}.
+ * This is used for sharing common localization grids between different netCDF
files.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+final class GridCacheValue {
+ /**
+ * The transform from grid coordinates to geographic or projected
coordinates.
+ */
+ final MathTransform transform;
+
+ /**
+ * The target CRS of {@link #transform} if different than the CRS inferred
by {@link CRSBuilder}.
+ * This field is non-null if the target CRS has been changed by
application of a linearizer.
+ */
+ private CoordinateReferenceSystem targetCRS;
+
+ /**
+ * Creates a new "grid to CRS" together with target CRS.
+ */
+ GridCacheValue(final Set<Linearizer> linearizers, final
LocalizationGridBuilder grid,
+ final MathTransformFactory factory) throws FactoryException
+ {
+ transform = grid.create(factory);
+ grid.linearizer(true).ifPresent((e) -> {
+ final String name = e.getKey();
+ for (final Linearizer linearizer : linearizers) {
+ if (name.equals(linearizer.name())) {
+ targetCRS = linearizer.getTargetCRS();
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * Adds the target CRS to the given list if that CRS is different than the
CRS inferred by {@link CRSBuilder}.
+ * This is an element of the list to provide to {@link
CRSBuilder#assemble(Decoder, Grid, List, Matrix)}.
+ */
+ final void getLinearizationTarget(final List<CoordinateReferenceSystem>
list) {
+ if (targetCRS != null) {
+ list.add(targetCRS);
+ }
+ }
+}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
index eaa8cc1..5a0f50f 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java
@@ -19,153 +19,231 @@ package org.apache.sis.internal.netcdf;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
-import org.opengis.util.FactoryException;
-import org.opengis.parameter.ParameterValueGroup;
+import java.util.List;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+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.referencing.operation.matrix.Matrix3;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
-import org.apache.sis.internal.metadata.ReferencingServices;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.util.logging.Logging;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.internal.util.Strings;
+import org.apache.sis.internal.referencing.AxisDirections;
+import org.apache.sis.storage.DataStoreReferencingException;
/**
- * Two-dimensional non-linear transforms to try in attempts to make a
localization grid more linear.
+ * A two-dimensional non-linear transform to try in an attempt to make a
localization grid more linear.
* Non-linear transforms are tested in "trials and errors" and the one
resulting in best correlation
- * coefficients is selected. This enumeration identifies which linearizers to
try for a given file.
- *
- * <p>When a non-linear transform exists in spherical or ellipsoidal variants,
we use the spherical
- * formulas instead than ellipsoidal formulas because the spherical ones are
faster and more stable
- * (because the inverse transforms are exact, up to rounding errors). The
errors caused by the use
- * of spherical formulas are compensated by the localization grid used after
the linearizer.</p>
+ * coefficients is selected.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
*
- * @see
org.apache.sis.referencing.operation.builder.LocalizationGridBuilder#addLinearizers(Map,
int...)
+ * @see
org.apache.sis.referencing.operation.builder.LocalizationGridBuilder#addLinearizers(Map,
boolean, int...)
*
* @since 1.0
* @module
*/
-public enum Linearizer {
+public final class Linearizer {
+ /**
+ * The datum to use as one of the predefined constants. The ellipsoid size
do not matter
+ * because a linear regression will be applied anyway. However the
eccentricity matter.
+ *
+ * <p>When a non-linear transform exists in spherical or ellipsoidal
variants, it may be sufficient to use
+ * the spherical formulas instead than ellipsoidal formulas because the
spherical ones are faster and more
+ * stable (because the inverse transforms are exact, up to rounding
errors). The errors caused by the use
+ * of spherical formulas are compensated by the localization grid used
after the linearizer.
+ * Spherical formulas can be requested by setting this field to {@link
CommonCRS#SPHERE}.</p>
+ *
+ * @see Convention#defaultHorizontalCRS(boolean)
+ */
+ private final CommonCRS datum;
+
+ /**
+ * The type of projection to create.
+ * Current implementation supports only Universal Transverse Mercator
(UTM) projection,
+ * but we nevertheless define this enumeration as a place-holder for more
types in the future.
+ */
+ public enum Type {
+ /**
+ * Universal Transverse Mercator projection.
+ */
+ UTM
+ }
+
+ /**
+ * The type of projection to create (Mercator, UTM, <i>etc</i>).
+ */
+ final Type type;
+
/**
- * Mercator (Spherical) projection. Inputs are latitude and longitude in
any order (axis order will
- * be detected by inspection of {@link Axis} elements). Outputs are
projected coordinates.
+ * The target coordinate reference system after application of the
non-linear transform.
*/
- MERCATOR("Mercator (Spherical)"),
+ private CoordinateReferenceSystem targetCRS;
/**
- * Satellite ground track.
+ * Creates a new linearizer working on the specified datum.
+ *
+ * @param datum the datum to use. Should be consistent with {@link
Convention#defaultHorizontalCRS(boolean)}.
+ * @param type the type of projection to create (Mercator, UTM,
<i>etc</i>).
*/
- GROUND_TRACK(null);
+ public Linearizer(final CommonCRS datum, final Type type) {
+ this.datum = datum;
+ this.type = type;
+ }
/**
- * The map projection method to use for constructing {@link #transform},
or {@code null} if the operation
- * is not a map projection or has already be constructed. If non-null,
the identified operation requires
- * (<var>longitude</var>, <var>latitude</var>) axis order.
+ * Returns the name used for identifying this linearizer in {@link
LocalizationGridBuilder}.
*/
- private String projection;
+ final String name() {
+ return type.name();
+ }
/**
- * The transform to apply, or {@code null} if none or not yet created.
This is created by {@link #transform()}
- * when first needed. The value after initialization may still be {@code
null} if initialization failed.
+ * Returns the target CRS computed by {@link #gridToTargetCRS
gridToTargetCRS(…)}.
*/
- private MathTransform transform;
+ final CoordinateReferenceSystem getTargetCRS() {
+ return targetCRS;
+ }
/**
- * Creates a new linearizer for the given projection method.
+ * Returns a string representation for debugging purposes.
*/
- private Linearizer(final String projection) {
- this.projection = projection;
+ @Override
+ public String toString() {
+ return Strings.toString(getClass(), "type", type, "targetCRS",
IdentifiedObjects.getName(targetCRS, null));
}
/**
- * Returns the hard-coded transform represented by this enumeration that
may help to make a localization grid
- * more linear.
+ * Creates a transform for the given localization grid. The returned
transform expects source coordinates in
+ * (latitude, longitude) or (longitude, latitude) order, depending on
{@code xdim} and {@code ydim} values.
+ * Target coordinates will be in the order defined by {@link #targetCRS},
which is assigned by this method.
*
- * @return the transform, or {@code null} if it can not be built.
+ * @param grid the grid on which to add non-linear transform.
+ * @param xdim index of longitude dimension in the grid control points.
+ * @param ydim index of latitude dimension in the grid control points.
+ * @return a two-dimensional transform expecting source coordinates in
(longitude, latitude) order.
+ * @throws TransformException if grid coordinates can not be obtained.
Actually this exception
+ * should never happen because the {@code MathTransform} used is a
linear transform.
*/
- private synchronized MathTransform transform() {
- final String p = projection;
- if (p != null) {
- projection = null; // Set to null now
in case of failure.
- final MathTransformFactory factory =
DefaultFactories.forClass(MathTransformFactory.class);
- if (factory != null) try { // Should never be null, but be
tolerant to configuration oddity.
- /*
- * The exact value of sphere radius does not matter because a
linear regression will
- * be applied anyway. However it matter to define a sphere
instead than an ellipsoid
- * because the spherical equations are simpler (consequently
faster and more stable).
- */
- final ParameterValueGroup pg = factory.getDefaultParameters(p);
-
pg.parameter(Constants.SEMI_MAJOR).setValue(ReferencingServices.AUTHALIC_RADIUS);
-
pg.parameter(Constants.SEMI_MINOR).setValue(ReferencingServices.AUTHALIC_RADIUS);
- transform = factory.createParameterizedTransform(pg);
- } catch (FactoryException e) {
- warning(e);
+ private MathTransform gridToTargetCRS(final LocalizationGridBuilder grid,
final int xdim, final int ydim)
+ throws TransformException
+ {
+ MathTransform transform;
+ switch (type) {
+ default: {
+ throw new AssertionError(type);
}
+ /*
+ * Create a Universal Transverse Mercator (UTM) projection
+ * for the zone containing a point in the middle of the grid.
+ */
+ case UTM: {
+ final Envelope bounds = grid.getSourceEnvelope(false);
+ final double[] median = grid.getControlPoint(
+ (int) Math.round(bounds.getMedian(0)),
+ (int) Math.round(bounds.getMedian(1)));
+ ProjectedCRS crs = datum.universal(median[ydim], median[xdim]);
+ transform = crs.getConversionFromBase().getMathTransform();
+ targetCRS = crs;
+ break;
+ }
+ }
+ /*
+ * Above transform expects (latitude, longitude) inputs. If grid
coordinates
+ * are in (longitude, latitude) order, we need to swap input
coordinates.
+ */
+ if (xdim < ydim) {
+ final Matrix3 m = new Matrix3();
+ m.m00 = m.m11 = 0;
+ m.m01 = m.m10 = 1;
+ transform = MathTransforms.concatenate(MathTransforms.linear(m),
transform);
}
return transform;
}
/**
* Applies non-linear transform candidates to the given localization grid.
+ * This method tries to locate longitude and latitude axes. If those axes
are found,
+ * they will be used as input coordinates for the {@link MathTransform}
instances
+ * (typically map projections) created by {@link #gridToTargetCRS
gridToTargetCRS(…)}.
+ * Those transforms are then {@linkplain
LocalizationGridBuilder#addLinearizers given
+ * to the localization grid} for consideration in attempts to make the
grid more linear.
*
- * @param factory the factory to use for creating transforms.
+ * @param sourceAxes coordinate system axes in CRS order.
* @param linearizers the linearizers to apply.
* @param grid the grid on which to add non-linear transform
candidates.
- * @param axes coordinate system axes in CRS order.
+ * @throws TransformException if grid coordinates can not be obtained.
Actually this exception should never
+ * happen because the {@code MathTransform} used is a linear
transform. We propagate this exception
+ * because it is more convenient to have it handled by the caller
together with other exceptions.
*/
- static void applyTo(final Set<Linearizer> linearizers, final
MathTransformFactory factory,
- final LocalizationGridBuilder grid, final Axis... axes)
+ static void setCandidatesOnGrid(final Axis[] sourceAxes, final
Set<Linearizer> linearizers, final LocalizationGridBuilder grid)
+ throws TransformException
{
int xdim = -1, ydim = -1;
- for (int i=axes.length; --i >= 0;) {
- switch (axes[i].abbreviation) {
+ for (int i=sourceAxes.length; --i >= 0;) {
+ switch (sourceAxes[i].abbreviation) {
case 'λ': xdim = i; break;
case 'φ': ydim = i; break;
}
}
- if (xdim >= 0 && ydim >= 0) {
+ if ((xdim | ydim) >= 0) {
final Map<String,MathTransform> projections = new HashMap<>();
for (final Linearizer linearizer : linearizers) {
- MathTransform transform;
- switch (linearizer) {
- default: {
- transform = linearizer.transform();
- break;
- }
- /*
- * Some special cases require information about the
particular grid we have at hand.
- * Only one case for now, but more cases may be added here
in the future.
- */
- case GROUND_TRACK: {
- int direction = axes[ydim].getMainDirection(); //
Fastest varying dimension (in netCDF order) of latitude.
- direction ^= 1; //
Convert netCDF order to CRS order.
- try {
- transform = SatelliteGroundTrack.create(factory,
grid, xdim, direction);
- } catch (FactoryException | TransformException e) {
- transform = null;
- warning(e);
- }
- break;
- }
- }
- if (transform != null) {
- projections.put(linearizer.name(), transform);
- }
+ final MathTransform transform =
linearizer.gridToTargetCRS(grid, xdim, ydim);
+ projections.put(linearizer.name(), transform);
}
- grid.addLinearizers(projections, xdim, ydim);
+ grid.addLinearizers(projections, false, Math.min(xdim, ydim),
Math.max(xdim, ydim));
}
}
/**
- * Reports a warning as originating from {@link
Variable#getGridGeometry()}, because it is the caller
- * (indirectly) for this class. The warning reported to this method should
never happen. But if one
- * happens anyway, do not cause the whole netCDF reader to fail for all
files because of this error.
+ * Given CRS components inferred by {@link CRSBuilder}, replaces CRS
components in the dimensions
+ * where linearization has been applied. The CRS components to replace are
inferred from axis directions.
+ *
+ * @param components the components of the compound CRS that
{@link CRSBuilder} inferred.
+ * @param replacements the {@link #targetCRS} of linearizations.
+ * @param reorderGridToCRS an affine transform doing a final step in a
"grid to CRS" transform for ordering axes.
+ * Not used by this method, but modified for taking in account
axis order changes caused by replacements.
*/
- private static void warning(final Exception e) {
- Logging.unexpectedException(Decoder.LOGGER, Variable.class,
"getGridGeometry", e);
+ static void replaceInCompoundCRS(final SingleCRS[] components,
+ final List<CoordinateReferenceSystem> replacements, final Matrix
reorderGridToCRS)
+ throws DataStoreReferencingException
+ {
+ Matrix original = null;
+search: for (final CoordinateReferenceSystem targetCRS : replacements) {
+ int firstDimension = 0;
+ for (int i=0; i < components.length; i++) {
+ final SingleCRS sourceCRS = components[i];
+ final int[] r =
AxisDirections.indicesOfColinear(sourceCRS.getCoordinateSystem(),
targetCRS.getCoordinateSystem());
+ if (r != null) {
+ for (int j=0; j<r.length; j++) {
+ if (r[j] != j) {
+ final int oldRow = r[j] + firstDimension;
+ final int newRow = j + firstDimension;
+ if (original == null) {
+ original = reorderGridToCRS.clone();
+ }
+ for (int k = original.getNumCol(); --k >= 0;) {
+ reorderGridToCRS.setElement(newRow, k,
original.getElement(oldRow, k));
+ }
+ }
+ }
+ components[i] = (ProjectedCRS) targetCRS;
+ continue search;
+ }
+ firstDimension +=
sourceCRS.getCoordinateSystem().getDimension();
+ }
+ // If a replacement can not be applied, fail CRS construction.
+ // May be relaxed in a future version if we have a use case.
+ throw new DataStoreReferencingException(Resources.format(
+ Resources.Keys.CanNotInjectComponent_1,
IdentifiedObjects.getName(targetCRS, null)));
+ }
}
}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
index 4a8bfa8..2cba062 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
@@ -79,6 +79,11 @@ public final class Resources extends IndexedResourceBundle {
public static final short CanNotCreateGridGeometry_3 = 12;
/**
+ * Can not inject component “{0}” in the reference system.
+ */
+ public static final short CanNotInjectComponent_1 = 26;
+
+ /**
* Can not relate dimension “{2}” of variable “{1}” to a coordinate
system dimension in netCDF
* file “{0}”.
*/
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
index 931f669..f3ebbb8 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
@@ -23,6 +23,7 @@ AmbiguousAxisDirection_4 = NetCDF file
\u201c{0}\u201d provides an ambi
CanNotComputeVariablePosition_2 = Can not compute data location for
\u201c{1}\u201d variable in the \u201c{0}\u201d netCDF file.
CanNotCreateCRS_3 = Can not create the Coordinate Reference
System for \u201c{1}\u201d in the \u201c{0}\u201d netCDF file. The reason is:
{2}
CanNotCreateGridGeometry_3 = Can not create the grid geometry
\u201c{1}\u201d in the \u201c{0}\u201d netCDF file. The reason is: {2}
+CanNotInjectComponent_1 = Can not inject component \u201c{0}\u201d
in the reference system.
CanNotRelateVariableDimension_3 = Can not relate dimension \u201c{2}\u201d
of variable \u201c{1}\u201d to a coordinate system dimension in netCDF file
\u201c{0}\u201d.
CanNotRender_2 = Can not render an image for
\u201c{0}\u201d. The reason is: {1}
CanNotSetProjectionParameter_5 = Can not set map projection parameter
\u201c{1}\u200b:{2}\u201d = {3} in the \u201c{0}\u201d netCDF file. The reason
is: {4}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
index dfb79b6..10add51 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
@@ -28,6 +28,7 @@ AmbiguousAxisDirection_4 = Le fichier netCDF
\u00ab\u202f{0}\u202f\u00b
CanNotComputeVariablePosition_2 = Ne peut pas calculer la position des
donn\u00e9es de la variable \u00ab\u202f{1}\u202f\u00bb dans le fichier netCDF
\u00ab\u202f{0}\u202f\u00bb.
CanNotCreateCRS_3 = Ne peut pas cr\u00e9er le syst\u00e8me de
r\u00e9f\u00e9rence des coordonn\u00e9es pour \u00ab\u202f{1}\u202f\u00bb dans
le fichier netCDF \u00ab\u202f{0}\u202f\u00bb. La raison est\u00a0: {2}
CanNotCreateGridGeometry_3 = Ne peut pas cr\u00e9er la
g\u00e9om\u00e9trie de grille \u00ab\u202f{1}\u202f\u00bb dans le fichier
netCDF \u00ab\u202f{0}\u202f\u00bb. La raison est\u00a0: {2}
+CanNotInjectComponent_1 = Ne peut pas pas ins\u00e9rer la composante
\u00ab\u202f{0}\u202f\u00bb dans le syst\u00e8me de r\u00e9f\u00e9rence.
CanNotRelateVariableDimension_3 = Ne peut pas relier la dimension
\u00ab\u202f{2}\u202f\u00bb de la variable \u00ab\u202f{1}\u202f\u00bb \u00e0
une dimension d\u2019un syst\u00e8me de coordonn\u00e9es du fichier netCDF
\u00ab\u202f{0}\u202f\u00bb.
CanNotRender_2 = Ne peut pas produire une image pour
\u00ab\u202f{0}\u202f\u00bb. La raison est\u00a0: {1}
CanNotSetProjectionParameter_5 = Ne peut pas d\u00e9finir le param\u00e8tre
de projection \u00ab\u202f{1}\u200b:{2}\u202f\u00bb = {3} dans le fichier
netCDF \u00ab\u202f{0}\u202f\u00bb. La raison est\u00a0: {4}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/SatelliteGroundTrack.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/SatelliteGroundTrack.java
deleted file mode 100644
index d6eddf0..0000000
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/SatelliteGroundTrack.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * 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.netcdf;
-
-import org.opengis.util.FactoryException;
-import org.opengis.geometry.DirectPosition;
-import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.parameter.ParameterDescriptorGroup;
-import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.referencing.operation.Matrix;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.MathTransform2D;
-import org.opengis.referencing.operation.MathTransformFactory;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D;
-import org.apache.sis.referencing.operation.transform.ContextualParameters;
-import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
-import org.apache.sis.referencing.operation.matrix.MatrixSIS;
-import org.apache.sis.referencing.operation.matrix.Matrix2;
-import org.apache.sis.parameter.ParameterBuilder;
-import org.apache.sis.geometry.DirectPosition2D;
-import org.apache.sis.internal.util.DoubleDouble;
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.math.Vector;
-import org.apache.sis.math.Line;
-
-
-/**
- * An estimation of the position of the satellite for given row and column
indices.
- * The calculation done in this class is very rough; the intent is not to give
an exact answer,
- * but to convert grid indices to something roughly proportional to latitudes
and longitudes
- * in order to make {@link LocalizationGridBuilder} work easier.
- *
- * <p>Current implementation is similar to a <a
href="https://en.wikipedia.org/wiki/Sinusoidal_projection">sinusoidal
projection</a>
- * in which the central meridian is oblique. That "oblique central meridian"
is fitted (by linear regression) to the presumed
- * satellite trajectory. This model may change in any future SIS version.</p>
- *
- * @author Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since 1.0
- * @module
- */
-final class SatelliteGroundTrack extends AbstractMathTransform2D {
- /**
- * Parameter descriptor for this transform.
- */
- private static final ParameterDescriptorGroup PARAMETERS;
- static {
- final ParameterBuilder builder = new
ParameterBuilder().setRequired(true);
- final ParameterDescriptor<?>[] grids = new ParameterDescriptor<?>[] {
- builder.addName(Constants.CENTRAL_MERIDIAN +
"_start").create(DirectPosition.class, null),
- builder.addName(Constants.CENTRAL_MERIDIAN + "_end")
.create(DirectPosition.class, null),
- };
- PARAMETERS = builder.addName("Satellite ground
track").createGroup(grids);
- }
-
- /**
- * Parameters describing (at least partially) this transform.
- * They are used for formatting <cite>Well Known Text</cite> (WKT).
- *
- * @see #getContextualParameters()
- */
- private final ContextualParameters context;
-
- /**
- * Terms of the λ = <var>slope</var>⋅φ + λ₀ equation estimating the
satellite longitude λ for a latitude φ.
- */
- private final double λ0, slope;
-
- /**
- * The inverse of this transform.
- */
- private final MathTransform2D inverse;
-
- /**
- * Creates a new instance of this transform.
- *
- * @param grid localization grid containing longitude and latitude
coordinates.
- * @param lonDim dimension of the longitude coordinates in the given
grid.
- * @param direction 0 if the ground track is on rows, of 1 if it is on
columns.
- */
- private SatelliteGroundTrack(final LocalizationGridBuilder grid, final int
lonDim, final int direction) throws TransformException {
- /*
- * We presume that the row or column in the middle of the localization
grid give the
- * coordinates that are closest to coordinates of the actual satellite
ground track.
- */
- final int median = (int)
grid.getSourceEnvelope(false).getMedian(direction ^ 1);
- final Vector longitudes, latitudes;
- if (direction == 0) {
- longitudes = grid.getRow(lonDim, median);
- latitudes = grid.getRow(lonDim ^ 1, median);
- } else {
- longitudes = grid.getColumn(lonDim, median);
- latitudes = grid.getColumn(lonDim ^ 1, median);
- }
- final Line line = new Line();
- line.fit(latitudes, longitudes);
- λ0 = line.y0(); // Longitude in
degrees.
- slope = Math.toRadians(line.slope()); // Take the slope
as if all latitudes were given in radians.
- inverse = new Inverse();
- context = new ContextualParameters(PARAMETERS, 2, 2);
- final MatrixSIS normalize =
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
- normalize.convertAfter(1, DoubleDouble.createDegreesToRadians(), null);
- setPositionParameter(Constants.CENTRAL_MERIDIAN + "_start", line,
latitudes.doubleValue(0));
- setPositionParameter(Constants.CENTRAL_MERIDIAN + "_end", line,
latitudes.doubleValue(latitudes.size() - 1));
- }
-
- /**
- * Sets the {@link DirectPosition} value of a parameter.
- */
- private void setPositionParameter(final String name, final Line line,
final double φ) {
- final DirectPosition2D pos = new DirectPosition2D(φ, line.y(φ));
- context.parameter(name).setValue(pos);
- }
-
- /**
- * Creates a new instance of this transform, or returns {@code null} if no
instance can be created.
- * The grid is presumed to contains latitude and longitude coordinates in
decimal degrees.
- *
- * @param factory the factory to use for creating transforms.
- * @param grid localization grid containing longitude and latitude
coordinates.
- * @param lonDim dimension of the longitude coordinates in the given
grid.
- * @param direction 0 if the ground track is on rows, of 1 if it is on
columns.
- */
- static MathTransform create(final MathTransformFactory factory, final
LocalizationGridBuilder grid,
- final int lonDim, final int direction) throws TransformException,
FactoryException
- {
- final SatelliteGroundTrack tr = new SatelliteGroundTrack(grid, lonDim,
direction);
- return tr.context.completeTransform(factory, tr);
- }
-
- /**
- * Returns the parameter values for this math transform.
- */
- @Override
- public ParameterValueGroup getParameterValues() {
- return context;
- }
-
- /**
- * Returns the parameters used for creating the complete transformation.
Those parameters describe a sequence
- * of <cite>normalize</cite> → {@code this} → <cite>denormalize</cite>
transforms.
- */
- @Override
- protected ContextualParameters getContextualParameters() {
- return context;
- }
-
- /**
- * Converts a single geographic coordinates into something hopefully more
proportional to grid indices.
- * For each coordinate tuple in {@code srcPts}, the first coordinate value
is longitude in degrees and
- * the second value is latitude in <strong>radians</strong>. The
conversion from degrees to radians is
- * done by the concatenated transform.
- */
- @Override
- public Matrix transform(final double[] srcPts, final int srcOff,
- final double[] dstPts, final int dstOff,
- final boolean derivate) throws TransformException
- {
- final double λ = srcPts[srcOff ];
- final double φ = srcPts[srcOff+1];
- final double cosφ = Math.cos(φ);
- final double m = φ * slope + λ0; // Central
meridian at the given latitude.
- final double Δλ = λ - m;
- if (dstPts != null) {
- dstPts[dstOff ] = Δλ * cosφ + m; // TODO: use
Math.fma with JDK9.
- dstPts[dstOff+1] = φ;
- }
- if (!derivate) {
- return null;
- }
- final Matrix2 d = new Matrix2();
- d.m00 = cosφ;
- d.m01 = slope * (1 - cosφ) - Δλ * Math.sin(φ);
- return d;
- }
-
- /**
- * Returns the inverse of this transform.
- */
- @Override
- public MathTransform2D inverse() {
- return inverse;
- }
-
- /**
- * The inverse of {@link SatelliteGroundTrack} transform.
- */
- private final class Inverse extends AbstractMathTransform2D.Inverse {
- /**
- * Creates a new instance of the inverse transform.
- */
- Inverse() {
- }
-
- /**
- * Returns the inverse of this transform, which is the enclosing
{@link SatelliteGroundTrack} transform.
- */
- @Override
- public MathTransform2D inverse() {
- return SatelliteGroundTrack.this;
- }
-
- /**
- * Converts grid indices to geographic coordinates (not necessarily in
degrees units).
- * See {@link SatelliteGroundTrack#transform(double[], int, double[],
int, boolean)}
- * for the units of measurement.
- */
- @Override
- public Matrix transform(final double[] srcPts, final int srcOff,
- final double[] dstPts, final int dstOff,
- final boolean derivate) throws
TransformException
- {
- final double x = srcPts[srcOff ];
- final double φ = srcPts[srcOff+1];
- final double cosφ = Math.cos(φ);
- final double m = φ * slope + λ0; // Central
meridian at the given latitude.
- final double Δx = x - m;
- if (dstPts != null) {
- dstPts[dstOff ] = Δx / cosφ + m;
- dstPts[dstOff+1] = φ;
- }
- if (!derivate) {
- return null;
- }
- final Matrix2 d = new Matrix2();
- d.m00 = 1 / cosφ;
- d.m01 = (Δx * Math.sin(φ) / cosφ - slope) / cosφ + slope;
- return d;
- }
- }
-}
diff --git
a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/SatelliteGroundTrackTest.java
b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/SatelliteGroundTrackTest.java
deleted file mode 100644
index fd39239..0000000
---
a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/SatelliteGroundTrackTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.netcdf;
-
-import java.awt.geom.AffineTransform;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.operation.MathTransformFactory;
-import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder;
-import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
-import org.apache.sis.referencing.operation.transform.CoordinateDomain;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.junit.Test;
-
-
-/**
- * Tests {@link SatelliteGroundTrack}. There is no external data that we can
use as a reference.
- * Consequently this test merely verifies that {@link SatelliteGroundTrack} is
self-consistent.
- *
- * @author Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since 1.0
- * @module
- */
-public final strictfp class SatelliteGroundTrackTest extends
MathTransformTestCase {
- /**
- * Size of the grid created for testing purpose.
- */
- private static final int WIDTH = 20, HEIGHT = 20;
-
- /**
- * Creates a transform for a grid of fixed size in a geographic domain.
- */
- private void createTransform() throws TransformException, FactoryException
{
- final LocalizationGridBuilder grid = new
LocalizationGridBuilder(WIDTH, HEIGHT);
- final AffineTransform tr =
AffineTransform.getRotateInstance(StrictMath.toRadians(31));
- tr.translate(-WIDTH / 2, -HEIGHT / 2);
- final double[] point = new double[2];
- for (int y=0; y<HEIGHT; y++) {
- for (int x=0; x<WIDTH; x++) {
- point[0] = x;
- point[1] = y;
- tr.transform(point, 0, point, 0, 1);
- grid.setControlPoint(x, y, point);
- }
- }
- transform =
SatelliteGroundTrack.create(DefaultFactories.forBuildin(MathTransformFactory.class),
grid, 1, 1);
- tolerance = 1E-12;
- derivativeDeltas = new double[] {0.1, 0.1};
- }
-
- /**
- * Tests self-consistency at random points.
- *
- * @throws FactoryException if an error occurred while creating the
transform.
- * @throws TransformException if an error occurred while transforming a
point.
- */
- @Test
- public void testConsistency() throws TransformException, FactoryException {
- createTransform();
- validate();
- verifyInDomain(CoordinateDomain.RANGE_10, -979924465940961910L);
- transform = transform.inverse();
- validate();
- verifyInDomain(CoordinateDomain.RANGE_10, -2122465178330330413L);
- }
-}
diff --git
a/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
b/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
index 64c89c2..b821ba5 100644
---
a/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
+++
b/storage/sis-netcdf/src/test/java/org/apache/sis/test/suite/NetcdfTestSuite.java
@@ -35,7 +35,6 @@ import org.junit.BeforeClass;
org.apache.sis.internal.netcdf.VariableTest.class,
org.apache.sis.internal.netcdf.AxisTest.class,
org.apache.sis.internal.netcdf.GridTest.class,
- org.apache.sis.internal.netcdf.SatelliteGroundTrackTest.class,
org.apache.sis.internal.netcdf.impl.ChannelDecoderTest.class,
org.apache.sis.internal.netcdf.impl.VariableInfoTest.class,
org.apache.sis.internal.netcdf.impl.GridInfoTest.class,