You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/02/28 12:14:49 UTC

(sis) branch geoapi-4.0 updated (3164e8db34 -> cc3e285243)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from 3164e8db34 When opening a large image in the JavaFX application, if there is no overview, zoom in the middle of the image as an initial view. We do that for avoiding to load too much data when the initial view is the whole image.
     new fcb23b6f67 Safety against resolution of zero during metadata construction.
     new 0a53a466e3 Add a safety against singular matrices read from GeoTIFF files.
     new cc3e285243 Vertical resolution was fetched from the old dimension when z is not the third axis.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../sis/referencing/operation/matrix/Matrices.java | 62 ++++++++++++++++++----
 .../referencing/operation/matrix/MatricesTest.java | 16 ++++++
 .../geotiff/reader/GridGeometryBuilder.java        | 13 ++++-
 .../sis/storage/geotiff/writer/GeoEncoder.java     | 11 ++--
 .../apache/sis/storage/base/MetadataBuilder.java   |  8 +--
 5 files changed, 91 insertions(+), 19 deletions(-)


(sis) 02/03: Add a safety against singular matrices read from GeoTIFF files.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 0a53a466e34dcb3ab390f4f1313aff3408215985
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Feb 28 12:17:02 2025 +0100

    Add a safety against singular matrices read from GeoTIFF files.
---
 .../sis/referencing/operation/matrix/Matrices.java | 62 ++++++++++++++++++----
 .../referencing/operation/matrix/MatricesTest.java | 16 ++++++
 .../geotiff/reader/GridGeometryBuilder.java        | 13 ++++-
 .../sis/storage/geotiff/writer/GeoEncoder.java     |  6 ++-
 4 files changed, 84 insertions(+), 13 deletions(-)

diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
index f058efe7bf..85960cec2e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -75,7 +75,7 @@ import org.opengis.coordinate.MismatchedDimensionException;
  * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.parameter.TensorParameters
  *
@@ -891,6 +891,46 @@ public final class Matrices extends Static {
         return changed;
     }
 
+    /**
+     * Forces the matrix to have at least one non-zero coefficient in every row, assuming an affine transform.
+     * The last column (the translation terms) and the last row (the [0 0 … 1] terms) are ignored.
+     * If a row contains only zero values (ignoring the translation term),
+     * then this method sets one element of that row to the given {@code defaultValue}.
+     * That modification occurs in the first free column, i.e. a column having no non-zero value.
+     * If no such free column is found, then this method stops the operation and returns {@code false}.
+     *
+     * @param  matrix        the matrix in which to force non-zero scale factors. Will be modified in-place.
+     * @param  defaultValue  the scale factor to assign to rows that do not have a non-zero scale factor.
+     * @return {@code true} on success (including when this method does nothing), or
+     *         {@code false} if this method cannot complete the operation.
+     *
+     * @since 1.5
+     */
+    public static boolean forceNonZeroScales(final Matrix matrix, final double defaultValue) {
+        final int numCol = matrix.getNumCol() - 1;
+        final int numRow = matrix.getNumRow() - 1;
+        int freeColumn = 0;
+okay:   for (int j=0; j<numRow; j++) {
+            for (int i=0; i<numCol; i++) {
+                if (matrix.getElement(j, i) != 0) {
+                    continue okay;
+                }
+            }
+search:     while (freeColumn < numCol) {
+                for (int i=0; i<numRow; i++) {
+                    if (matrix.getElement(i, freeColumn) != 0) {
+                        freeColumn++;
+                        continue search;
+                    }
+                }
+                matrix.setElement(j, freeColumn++, defaultValue);
+                continue okay;
+            }
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Returns {@code true} if the given matrix is likely to use extended precision.
      * A value of {@code true} is not a guarantee that the matrix uses extended precision,
@@ -1000,7 +1040,7 @@ public final class Matrices extends Static {
         if (numRow == numCol) {
             return Solver.inverse(matrix);
         }
-        final NonSquareMatrix result = new NonSquareMatrix(numRow, numCol, false);
+        final var result = new NonSquareMatrix(numRow, numCol, false);
         result.setMatrix(matrix);
         return result.inverse();
     }
@@ -1221,13 +1261,13 @@ public final class Matrices extends Static {
     public static String toString(final Matrix matrix) {
         final int numRow = matrix.getNumRow();
         final int numCol = matrix.getNumCol();
-        final String[]  elements            = new String [numCol * numRow];     // String representation of matrix values.
-        final boolean[] noFractionDigits    = new boolean[numCol * numRow];     // Whether to remove the trailing ".0" for a given number.
-        final boolean[] hasDecimalSeparator = new boolean[numCol];              // Whether the column has at least one number where fraction digits are shown.
-        final byte[] maximumFractionDigits  = new byte   [numCol];              // The greatest number of fraction digits found in a column.
-        final byte[] maximumPaddingZeros    = new byte   [numCol * numRow];     // Maximal number of zeros that we can append before to exceed the IEEE 754 accuracy.
-        final byte[] widthBeforeFraction    = new byte   [numCol];              // Number of characters before the fraction digits: spacing + ('-') + integerDigits + '.'
-        final byte[] columnWidth            = new byte   [numCol];              // Total column width.
+        final var elements              = new String [numCol * numRow];     // String representation of matrix values.
+        final var noFractionDigits      = new boolean[numCol * numRow];     // Whether to remove the trailing ".0" for a given number.
+        final var hasDecimalSeparator   = new boolean[numCol];              // Whether the column has at least one number where fraction digits are shown.
+        final var maximumFractionDigits = new byte   [numCol];              // The greatest number of fraction digits found in a column.
+        final var maximumPaddingZeros   = new byte   [numCol * numRow];     // Maximal number of zeros that we can append before to exceed the IEEE 754 accuracy.
+        final var widthBeforeFraction   = new byte   [numCol];              // Number of characters before the fraction digits: spacing + ('-') + integerDigits + '.'
+        final var columnWidth           = new byte   [numCol];              // Total column width.
         int totalWidth = 1;
         /*
          * Create now the string representation of all matrix elements and measure the width
@@ -1290,9 +1330,9 @@ public final class Matrices extends Static {
          * Now append the formatted elements with the appropriate number of spaces before each value,
          * and trailling zeros after each value except ±0, ±1, NaN and infinities.
          */
-        final String   lineSeparator = System.lineSeparator();
+        final String lineSeparator = System.lineSeparator();
         final CharSequence whiteLine = CharSequences.spaces(totalWidth);
-        final StringBuilder   buffer = new StringBuilder((totalWidth + 2 + lineSeparator.length()) * (numRow + 2));
+        final var buffer = new StringBuilder((totalWidth + 2 + lineSeparator.length()) * (numRow + 2));
         buffer.append('┌').append(whiteLine).append('┐').append(lineSeparator);
         int flatIndex = 0;
         for (int j=0; j<numRow; j++) {
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java
index 29b28f520f..252ae8862c 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/matrix/MatricesTest.java
@@ -442,6 +442,22 @@ public final class MatricesTest extends TestCase {
         }), matrix);
     }
 
+    /**
+     * Tests {@link Matrices#forceNonZeroScales(Matrix, double)}.
+     */
+    public void testForceNonZeroScales() {
+        MatrixSIS matrix = Matrices.create(4, 4, new double[] {
+            2, 0, 0, 8,
+            0, 0, 4, 7,
+            0, 0, 0, 6,
+            0, 0, 0, 1
+        });
+        MatrixSIS expected = matrix.clone();
+        expected.setElement(2, 1, 3);
+        assertTrue(Matrices.forceNonZeroScales(matrix, 3));
+        assertEquals(expected, matrix);
+    }
+
     /**
      * Tests {@link Matrices#equals(Matrix, Matrix, double, boolean)}.
      */
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
index bb64c6df01..7a7366e899 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
@@ -25,6 +25,7 @@ import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 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;
@@ -76,6 +77,14 @@ import org.apache.sis.math.Vector;
  * @author  Martin Desruisseaux (Geomatys)
  */
 public final class GridGeometryBuilder extends GeoKeysLoader {
+    /**
+     * Default scale factory to apply if a row in the model transformation contains only zero values.
+     * The matrix in a GeoTIFF file is always of size 4×4 even if the <abbr>CRS</abbr> is two-dimensional.
+     * In the latter case, the matrix row for the third dimension has only zero values. That row should be
+     * discarded when building the final {@link GridGeometry}, but there is sometime inconsistency between
+     * the number of <abbr>CRS</abbr> dimensions and which matrix rows have been assigned non-zero values.
+     */
+    private static final double DEFAULT_SCALE_FACTOR = 1;
 
     //  ╔════════════════════════════════════════════════════════════════════════════════╗
     //  ║                                                                                ║
@@ -300,7 +309,9 @@ public final class GridGeometryBuilder extends GeoKeysLoader {
         try {
             MathTransform gridToCRS = null;
             if (affine != null) {
-                gridToCRS = factory.createAffineTransform(Matrices.resizeAffine(affine, ++n, n));
+                final Matrix m = Matrices.resizeAffine(affine, ++n, n);
+                Matrices.forceNonZeroScales(m, DEFAULT_SCALE_FACTOR);
+                gridToCRS = factory.createAffineTransform(m);
             } else if (modelTiePoints != null) {
                 pixelIsPoint = true;
                 gridToCRS = Localization.nonLinear(modelTiePoints);
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
index fc61bfaef5..a0d4db0378 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
@@ -97,7 +97,11 @@ public final class GeoEncoder {
 
     /**
      * Size of the model transformation matrix, in number of rows and columns.
-     * This size is fixed by the GeoTIFF specification.
+     * This size is fixed by the GeoTIFF specification. If some rows need to
+     * be added for filling the space, they will be filled with zero values
+     * (i.e., the matrix will be singular until the reader removes those rows).
+     *
+     * @see org.apache.sis.storage.geotiff.reader.GridGeometryBuilder#DEFAULT_SCALE_FACTOR
      */
     private static final int MATRIX_SIZE = 4;
 


(sis) 03/03: Vertical resolution was fetched from the old dimension when z is not the third axis.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit cc3e285243c92b9bdd8184492d1411ca0c10aab7
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Feb 28 12:59:47 2025 +0100

    Vertical resolution was fetched from the old dimension when z is not the third axis.
---
 .../main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java       | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
index a0d4db0378..7337852ab3 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
@@ -40,6 +40,7 @@ import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.VerticalCS;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.datum.GeodeticDatum;
@@ -294,7 +295,7 @@ public final class GeoEncoder {
         final CoordinateReferenceSystem fullCRS = grid.getCoordinateReferenceSystem();
         final VerticalCRS vertical = CRS.getVerticalComponent(fullCRS, true);
         if (vertical != null) {
-            final CoordinateSystem cs = vertical.getCoordinateSystem();
+            final VerticalCS cs = vertical.getCoordinateSystem();
             final int verticalDimension = AxisDirections.indexOfColinear(fullCRS.getCoordinateSystem(), cs);
             if (verticalDimension >= 0 && Arrays.binarySearch(dimensions, verticalDimension) < 0) {
                 writeCRS(vertical);
@@ -311,7 +312,7 @@ public final class GeoEncoder {
                             case MATRIX_SIZE-1: s = more.getNumCol() - 1; break;    // Translation.
                         }
                         // Copy the rows of the third dimension.
-                        gridToCRS.setElement(BIDIMENSIONAL, i, more.getElement(BIDIMENSIONAL, s));
+                        gridToCRS.setElement(BIDIMENSIONAL, i, more.getElement(verticalDimension, s));
                     }
                 }
             }


(sis) 01/03: Safety against resolution of zero during metadata construction.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit fcb23b6f6745361d72b88034ff22f4ea41528253
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Feb 28 10:44:19 2025 +0100

    Safety against resolution of zero during metadata construction.
---
 .../main/org/apache/sis/storage/base/MetadataBuilder.java         | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
index 6d4cefd9cb..f4ed3b4507 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
@@ -2019,7 +2019,7 @@ public class MetadataBuilder {
                 final double distance = unit.getConverterToAny(targetUnit).convert(resolution[i]);
                 if (setter == null) {
                     addTemporalResolution(distance);
-                } else if (Double.isFinite(distance)) {
+                } else if (distance > 0 && distance != Double.POSITIVE_INFINITY) {
                     var r = new DefaultResolution();
                     setter.accept(r, shared(distance));
                     addIfNotPresent(identification().getSpatialResolutions(), r);
@@ -2060,7 +2060,7 @@ public class MetadataBuilder {
      * @param  duration  the resolution in days, or {@code NaN} for no-operation.
      */
     public final void addTemporalResolution(final double duration) {
-        if (Double.isFinite(duration)) {
+        if (duration > 0 && duration != Double.POSITIVE_INFINITY) {
             addIfNotPresent(identification().getTemporalResolutions(),
                     Duration.ofNanos(Math.round(duration * Constants.NANOSECONDS_PER_DAY)));
         }
@@ -2261,7 +2261,7 @@ public class MetadataBuilder {
 
     /**
      * Sets the degree of detail in the given dimension.
-     * This method does nothing if the given resolution if NaN or infinite.
+     * This method does nothing if the given resolution is NaN or infinite.
      * Storage location is:
      *
      * <ul>
@@ -2273,7 +2273,7 @@ public class MetadataBuilder {
      * @param  unit        the resolution unit, of {@code null} if unknown.
      */
     public final void setAxisResolution(final int dimension, double resolution, final Unit<?> unit) {
-        if (Double.isFinite(resolution)) {
+        if (resolution > 0 && resolution != Double.POSITIVE_INFINITY) {
             /*
              * Value should be a Quantity<?>. Since GeoAPI does not yet allow that,
              * we convert to metres for now. Future version should store the value