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 4aef90224e Bug fixes in the reduction of dimensions of a wraparound
transforms. Improve the coverage of the JUnit test.
4aef90224e is described below
commit 4aef90224ea0f3db238bb2fe64bb28e9f33435d3
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Jan 24 16:38:38 2026 +0100
Bug fixes in the reduction of dimensions of a wraparound transforms.
Improve the coverage of the JUnit test.
---
.../operation/transform/TransformJoiner.java | 73 ++++++++++++++++++---
.../transform/WraparoundTransformTest.java | 76 ++++++++++++++++++----
2 files changed, 128 insertions(+), 21 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/TransformJoiner.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/TransformJoiner.java
index afa819d7c5..42ab864fca 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/TransformJoiner.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/TransformJoiner.java
@@ -198,6 +198,23 @@ valid: if (i >= 0 && i < steps.size()) {
return
MathTransforms.getMatrix(getTransform(relativeIndex).orElse(null));
}
+ /**
+ * Creates a diagonal matrix of the dimensions of the transform at the
given relative index.
+ *
+ * @param relativeIndex index of the matrix to get,
+ * relative to the transform on which {@code tryConcatenate(…)}
has been invoked.
+ * @return a diagonal matrix for the number of dimensions of the transform
at the given index.
+ */
+ private Matrix createDiagonalMatrix(final int relativeIndex) {
+ final MathTransform step = getTransform(relativeIndex).orElseThrow();
+ final int srcDim = step.getSourceDimensions();
+ final int tgtDim = step.getTargetDimensions();
+ final Matrix matrix = Matrices.createDiagonal(tgtDim + 1, srcDim + 1);
+ if (tgtDim < srcDim) matrix.setElement(tgtDim, tgtDim, 0);
+ matrix.setElement(tgtDim, srcDim, 1);
+ return matrix;
+ }
+
/**
* If the specified range of dimensions is unused by the specified
neighbor transform, removes those dimensions.
* A range of dimensions is considered unused by a neighbor if
@@ -573,25 +590,43 @@ valid: if (i >= 0 && i < steps.size()) {
* The unsafe case of the former was handled in code above by removing
the keys that are too large.
*/
final MatrixSIS simplified = Matrices.copy(moveFromBeforeToAfter ?
before : after);
+ final int lastSimplifiedRow = simplified.getNumRow() - 1;
+ final int lastSimplifiedCol = simplified.getNumCol() - 1;
final int dim = moveFromBeforeToAfter ? after.getNumCol() :
before.getNumRow();
final MatrixSIS moved = Matrices.create(dim, dim,
ExtendedPrecisionMatrix.CREATE_IDENTITY);
for (final Map.Entry<Integer, Integer> entry : dimensions.entrySet()) {
final int srcRow = entry.getKey();
final int tgtRow = entry.getValue();
- moved.setElement(tgtRow, tgtRow, 0); // Remove the 1 on the
diagonal for that row.
+ moved.setElement(tgtRow, tgtRow, 0); // Clear the diagonal for
having 0 in all columns of this row.
/*
* See the comment in `removeInvalidPassThrough(…)` for an
explanation about why we need this check.
- * Note: when `moveFromBeforeToAfter` is false (which is the only
case where we need bound check),
- * `simplified` is of the size of `after`.
+ * Summary: in the "move after to before" case, the `simplified`
matrix is the `after` matrix and may
+ * be reducing the number of dimensions, in which case it has less
rows than `moved`. It is okay when
+ * the rows were only pass-through dimensions.
*/
- if (moveFromBeforeToAfter || srcRow < simplified.getNumRow()) {
+ if (moveFromBeforeToAfter || srcRow <= lastSimplifiedRow) {
dimensions.forEach((srcCol, tgtCol) -> {
moved.setNumber(tgtRow, tgtCol,
simplified.getElement(srcRow, srcCol));
});
- // Simplify the row only after all elements have been copied.
- for (int i = simplified.getNumCol(); --i >= 0;) {
- simplified.setElement(srcRow, i, (i == srcRow) ? 1 : 0);
- }
+ // Set the row to an identity operation only after all
elements have been copied.
+ for (int i = 0; i <= lastSimplifiedCol; i++)
simplified.setElement(srcRow, i, 0);
+ simplified.setElement(srcRow, (srcRow != lastSimplifiedRow) ?
srcRow : lastSimplifiedCol, 1);
+ }
+ }
+ /*
+ * The last row of the simplified matrix should have been moved to the
last row of the `moved` matrix.
+ * That row is not associated to a dimension of coordinate tuples, and
is usually only [0 0 0 … 0 1].
+ * The row is already at the correct location if `simplified` is
square, but needs to be moved if the
+ * `simplified` matrix is reducing the number of dimensions (i.e. has
less rows than `moved` matrix).
+ * Implementation note: we don't proceed by modifying `tgtRow` in
above loop because of complication
+ * that duplicated `tgtRow` values would cause.
+ */
+ final int lastRow = moved.getNumRow() - 1;
+ final int tgtRow = dimensions.getOrDefault(lastSimplifiedRow, lastRow);
+ if (tgtRow != lastRow) {
+ for (int i = moved.getNumCol(); --i >= 0;) {
+ moved.setNumber(lastRow, i, moved.getNumber(tgtRow, i));
+ moved.setElement(tgtRow, i, 0);
}
}
/*
@@ -737,7 +772,21 @@ valid: if (i >= 0 && i < steps.size()) {
* If one of above cases is detected, then this method moves partially the
linear step on the
* other side for reducing the number of dimensions on which the transform
at index 0 operates.
* Such reduction can help {@link TransformSeparator} to separate the
transform.
+ * The main transform is reduced by the {@code reducer} argument,
+ * which will receive the following arguments:
*
+ * <ul>
+ * <li>A {@link BitSet} with the dimensions to retain.</li>
+ * <li>An {@link Integer} with value -1 if the {@code BitSet} specifies
input dimensions,
+ * or +1 if the {@code BitSet} specifies output dimensions of the
transform.</li>
+ * </ul>
+ *
+ * The {@code reducer} shall return a transform with a number of input or
output dimensions
+ * (depending on the second argument) equals to {@link
BitSet#cardinality()}, or {@code null}
+ * if the reducer cannot build the transform.
+ *
+ * @param inputDimensions dimensions of coordinates used by the main
transform.
+ * @param outputDimensions dimensions of coordinates with a value
modified by the transform.
* @param reducer a constructor accepting selected dimension and
returning the new transform.
* @return whether the transform chain has been modified as a result of
this method call.
* @throws FactoryException if the concatenation is not a valid
replacement.
@@ -772,6 +821,7 @@ prepare: if (relativeIndex < 0) {
* We only need to keep the rows which modify the input
coordinates of the main transform.
*/
inputDimensions.forEach(rowsToKeep::set);
+ if (rowsToKeep.length() >= numCol) continue;
} else {
/*
* Case where `matrix` is after the main transform and reduces
the number of dimensions.
@@ -785,6 +835,11 @@ prepare: if (relativeIndex < 0) {
}
}
}
+ if (rowsToKeep.isEmpty()) {
+ if (replace(0,
factory.createAffineTransform(createDiagonalMatrix(0)))) {
+ return true;
+ }
+ }
/*
* Because `move` has too many columns compared to `keep`, we
will need to ignore some columns.
* We discard the columns where all coefficients are zero,
starting by the last column before
@@ -840,7 +895,7 @@ prepare: if (relativeIndex < 0) {
prefix = move;
suffix = keep;
}
- colsToKeep.clear(numCol - 1);
+ colsToKeep.clear(numCol - 1); // Ignore translation column or
last row of affine transform.
MathTransform concatenation = reducer.apply(colsToKeep,
relativeIndex);
if (concatenation != null) {
concatenation =
factory.createConcatenatedTransform(factory.createAffineTransform(prefix),
concatenation);
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
index 8da20c22ef..ba6dacc002 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java
@@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestCase;
import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import static org.opengis.test.Assertions.assertMatrixEquals;
@@ -195,20 +196,71 @@ public final class WraparoundTransformTest extends
TestCase {
/**
* Tests concatenation that reduces the number of dimensions.
- * The {@link WraparoundTransform#tryConcatenate(TransformJoiner)}
- * should replace the four-dimensional {@link WraparoundTransform}
- * by a two-dimensional one.
+ * The {@link WraparoundTransform#tryConcatenate(TransformJoiner)} method
should
+ * replace the four-dimensional {@link WraparoundTransform} by a
two-dimensional one.
*/
@Test
public void testDimensionReduction() {
- MathTransform tr = MathTransforms.translation(0, 0, 0, 0);
- tr = MathTransforms.concatenate(tr, new WraparoundTransform(4, 0, 360,
40));
- tr = MathTransforms.concatenate(tr,
MathTransforms.linear(Matrices.createDimensionSelect(4, new int[] {0, 1})));
- final var reduced = assertInstanceOf(WraparoundTransform.class,
MathTransforms.getLastStep(tr));
- assertEquals( 2, reduced.getSourceDimensions());
- assertEquals( 2, reduced.getTargetDimensions());
- assertEquals( 0, reduced.wraparoundDimension);
- assertEquals(360, reduced.period);
- assertEquals( 40, reduced.sourceMedian);
+ final MathTransform prefix = MathTransforms.translation(10, 7, 12, -4);
+ final MathTransform suffix =
MathTransforms.linear(Matrices.createDimensionSelect(4, new int[] {0, 1}));
+ for (int dim = 0; dim <= 3; dim++) {
+ final var wraparound = new WraparoundTransform(4, dim, 360, 40);
+ final MathTransform tr = MathTransforms.concatenate(prefix,
wraparound, suffix);
+ final var head = assertInstanceOf(LinearTransform.class,
MathTransforms.getFirstStep(tr));
+ if (dim >= 2) {
+ assertSame(head, tr);
+ } else {
+ final var reduced =
assertInstanceOf(WraparoundTransform.class, MathTransforms.getLastStep(tr));
+ assertEquals( 2, reduced.getSourceDimensions());
+ assertEquals( 2, reduced.getTargetDimensions());
+ assertEquals(dim, reduced.wraparoundDimension);
+ assertEquals(360, reduced.period);
+ assertEquals( 40, reduced.sourceMedian);
+ }
+ assertMatrixEquals(Matrices.create(3, 5, new double[] {
+ 1, 0, 0, 0, 10,
+ 0, 1, 0, 0, 7,
+ 0, 0, 0, 0, 1}), head.getMatrix(), "Dimension reduction");
+ }
+ }
+
+ /**
+ * Tests concatenation that increases the number of dimensions.
+ * The {@link WraparoundTransform#tryConcatenate(TransformJoiner)} method
should
+ * replace the four-dimensional {@link WraparoundTransform} by a
two-dimensional one.
+ */
+ @Test
+ public void testDimensionIncrease() {
+ final MatrixSIS diagonal = Matrices.createDiagonal(5, 3);
+ diagonal.setElement(2, 2, 0);
+ diagonal.setElement(4, 2, 1);
+ final MathTransform prefix = MathTransforms.linear(diagonal);
+ final MathTransform suffix = MathTransforms.translation(10, 7, 12, -4);
+ for (int dim = 0; dim <= 3; dim++) {
+ final var wraparound = new WraparoundTransform(4, dim, 360, 40);
+ final MathTransform tr = MathTransforms.concatenate(prefix,
wraparound, suffix);
+ if (dim >= 2) {
+ final List<MathTransform> steps = MathTransforms.getSteps(tr);
+ assertSame(prefix, steps.get(0));
+ assertSame(wraparound, steps.get(1));
+ assertSame(suffix, steps.get(2));
+ assertEquals(3, steps.size());
+ } else {
+ final var reduced =
assertInstanceOf(WraparoundTransform.class, MathTransforms.getFirstStep(tr));
+ assertEquals( 2, reduced.getSourceDimensions());
+ assertEquals( 2, reduced.getTargetDimensions());
+ assertEquals(dim, reduced.wraparoundDimension);
+ assertEquals(360, reduced.period);
+ assertEquals( 40, reduced.sourceMedian);
+
+ final var tail = assertInstanceOf(LinearTransform.class,
MathTransforms.getLastStep(tr));
+ assertMatrixEquals(Matrices.create(5, 3, new double[] {
+ 1, 0, 10,
+ 0, 1, 7,
+ 0, 0, 12,
+ 0, 0, -4,
+ 0, 0, 1}), tail.getMatrix(), "Dimension increase");
+ }
+ }
}
}