sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Allow some tolerance when comparing two `GridGeometry` objects. It allows to detect more easily when a `GridCoverage` can be used unchanged.
Date Mon, 30 Mar 2020 14:01:43 GMT
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 8ec46f6  Allow some tolerance when comparing two `GridGeometry` objects. It allows
to detect more easily when a `GridCoverage` can be used unchanged.
8ec46f6 is described below

commit 8ec46f6e77302fa408e1f2d26be582e3a6b7161a
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Mar 30 16:00:40 2020 +0200

    Allow some tolerance when comparing two `GridGeometry` objects.
    It allows to detect more easily when a `GridCoverage` can be used unchanged.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   | 37 ++++++++++++--
 .../org/apache/sis/coverage/grid/GridGeometry.java | 57 +++++++++++++++++++---
 .../sis/coverage/grid/ResampledGridCoverage.java   |  5 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  | 54 +++++++++++---------
 .../org/apache/sis/geometry/GeneralEnvelope.java   | 12 ++---
 5 files changed, 125 insertions(+), 40 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 21247d5..efbc37f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -55,6 +55,8 @@ import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
@@ -83,7 +85,7 @@ import org.opengis.coverage.PointOutsideCoverageException;
  * @since   1.0
  * @module
  */
-public class GridExtent implements GridEnvelope, Serializable {
+public class GridExtent implements GridEnvelope, LenientComparable, Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -1289,15 +1291,42 @@ public class GridExtent implements GridEnvelope, Serializable {
 
     /**
      * Compares the specified object with this grid envelope for equality.
+     * This method delegates to {@code equals(object, ComparisonMode.STRICT)}.
      *
      * @param  object  the object to compare with this grid envelope for equality.
      * @return {@code true} if the given object is equal to this grid envelope.
      */
     @Override
-    public boolean equals(final Object object) {
-        if (object != null && object.getClass() == GridExtent.class) {
+    public final boolean equals(final Object object) {
+        return equals(object, ComparisonMode.STRICT);
+    }
+
+    /**
+     * Compares the specified object with this grid envelope for equality.
+     * If the mode is {@link ComparisonMode#IGNORE_METADATA} or more flexible,
+     * then the {@linkplain #getAxisType(int) axis types} are ignored.
+     *
+     * @param  object  the object to compare with this grid envelope for equality.
+     * @param  mode    the strictness level of the comparison.
+     * @return {@code true} if the given object is equal to this grid envelope.
+     *
+     * @since 1.1
+     */
+    @Override
+    @SuppressWarnings("fallthrough")
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof GridExtent) {
             final GridExtent other = (GridExtent) object;
-            return Arrays.equals(coordinates, other.coordinates) && Arrays.equals(types,
other.types);
+            if (Arrays.equals(coordinates, other.coordinates)) {
+                switch (mode) {
+                    case STRICT:      if (!getClass().equals(object.getClass())) return false;
 // else fallthrough
+                    case BY_CONTRACT: if (!Arrays.equals(types, other.types))    return false;
 // else fallthrough
+                    default:          return true;
+                }
+            }
         }
         return false;
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 9794c29..bc2d7c8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.coverage.grid;
 
-import java.util.Objects;
 import java.util.Locale;
 import java.util.Optional;
 import java.time.Instant;
@@ -65,9 +64,12 @@ import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 import org.apache.sis.io.TableAppender;
@@ -109,7 +111,7 @@ import org.apache.sis.xml.NilReason;
  * @since   1.0
  * @module
  */
-public class GridGeometry implements Serializable {
+public class GridGeometry implements LenientComparable, Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -1364,17 +1366,60 @@ public class GridGeometry implements Serializable {
 
     /**
      * Compares the specified object with this grid geometry for equality.
+     * This method delegates to {@code equals(object, ComparisonMode.STRICT)}.
      *
      * @param  object  the object to compare with.
      * @return {@code true} if the given object is equals to this grid geometry.
      */
     @Override
     public boolean equals(final Object object) {
-        if (object != null && object.getClass() == getClass()) {
+        return equals(object, ComparisonMode.STRICT);
+    }
+
+    /**
+     * Compares the specified object with this grid geometry for equality.
+     * If the mode is {@link ComparisonMode#IGNORE_METADATA} or more flexible,
+     * then the {@linkplain GridExtent#getAxisType(int) axis types} are ignored.
+     *
+     * @param  object  the object to compare with this grid geometry for equality.
+     * @param  mode    the strictness level of the comparison.
+     * @return {@code true} if the given object is equal to this grid geometry.
+     *
+     * @since 1.1
+     */
+    @Override
+    @SuppressWarnings("fallthrough")
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof GridGeometry) {
             final GridGeometry that = (GridGeometry) object;
-            return Objects.equals(this.extent,    that.extent)    &&
-                   Objects.equals(this.gridToCRS, that.gridToCRS) &&
-                   Objects.equals(this.envelope,  that.envelope);
+            if ((mode != ComparisonMode.STRICT || getClass().equals(object.getClass()))
+                    && Utilities.deepEquals(extent,    that.extent,    mode)
+                    && Utilities.deepEquals(gridToCRS, that.gridToCRS, mode))
+            {
+                final ImmutableEnvelope othenv = that.envelope;
+                if (!mode.isApproximate()) {
+                    return Utilities.deepEquals(envelope, othenv, mode);
+                }
+                if ((envelope == null) == (othenv == null) &&
+                        Utilities.deepEquals(getCoordinateReferenceSystem(envelope),
+                                             getCoordinateReferenceSystem(othenv), mode))
+                {
+                    if (envelope != null) {
+                        for (int i=envelope.getDimension(); --i >= 0;) {
+                            final double ε = (resolution != null) ? resolution[i] * Numerics.COMPARISON_THRESHOLD
: 0;
+                            if (!MathFunctions.epsilonEqual(envelope.getLower(i), othenv.getLower(i),
ε) ||
+                                !MathFunctions.epsilonEqual(envelope.getUpper(i), othenv.getUpper(i),
ε))
+                            {
+                                return false;
+                            }
+                        }
+                    }
+                    return true;
+                }
+            }
         }
         return false;
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 3b2a1b1..2906aa6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -42,6 +42,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.ComparisonMode;
 
 
 /**
@@ -293,10 +294,12 @@ final class ResampledGridCoverage extends GridCoverage {
          * At this point all target grid geometry components are non-null.
          * Build the final target GridGeometry if any components were missing.
          */
+        ComparisonMode mode = ComparisonMode.IGNORE_METADATA;
         if (!target.isDefined(GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS | GridGeometry.CRS))
{
             target = new GridGeometry(targetExtent, PixelInCell.CELL_CENTER, targetGridToCRS,
targetCRS);
+            mode = ComparisonMode.APPROXIMATE;
         }
-        if (sourceGG.equals(target)) {
+        if (sourceGG.equals(target, mode)) {
             return source;
         }
         /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index 9893725..293e748 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -150,18 +150,22 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
     }
 
     /**
-     * Returns {@code true} if at least one of the specified CRS is null, or both CRS are
equals.
-     * This special processing for {@code null} values is different from the usual contract
of an
-     * {@code equals} method, but allow to handle the case where the CRS is unknown.
-     *
-     * <p>Note that in debug mode (to be used in assertions only), the comparisons
are actually a
-     * bit more relax than just "ignoring metadata", since some rounding errors are tolerated.</p>
+     * Weakly asserts that the given CRS are equal. This method may return {@code false}
without throwing
+     * {@link AssertionError}, so callers should still test the returned value in their {@code
assert} statement.
      */
-    static boolean equalsIgnoreMetadata(final CoordinateReferenceSystem crs1,
-                                        final CoordinateReferenceSystem crs2, final boolean
debug)
+    static boolean assertEquals(final CoordinateReferenceSystem crs1, final CoordinateReferenceSystem
crs2) {
+        return equals(crs1, crs2, ComparisonMode.DEBUG);
+    }
+
+    /**
+     * Returns {@code true} if at least one of the specified CRS is null, or both CRS are
approximately equals.
+     * This special processing for {@code null} values is different than the usual contract
of {@code equals} method,
+     * but allows to handle the case where the CRS is unknown.
+     */
+    private static boolean equals(final CoordinateReferenceSystem crs1,
+                                  final CoordinateReferenceSystem crs2, final ComparisonMode
mode)
     {
-        return (crs1 == null) || (crs2 == null) || Utilities.deepEquals(crs1, crs2,
-                debug ? ComparisonMode.DEBUG : ComparisonMode.IGNORE_METADATA);
+        return (crs1 == null) || (crs2 == null) || Utilities.deepEquals(crs1, crs2, mode);
     }
 
     /**
@@ -699,7 +703,7 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * then this method returns {@code false}.
      *
      * <h4>Pre-conditions</h4>
-     * This method assumes that the specified point uses the same CRS than this envelope.
+     * This method assumes that the specified point uses a CRS equivalent to this envelope
CRS.
      * For performance reasons, it will no be verified unless Java assertions are enabled.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -717,8 +721,7 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
         ensureNonNull("position", position);
         final int dimension = getDimension();
         ensureDimensionMatches("point", dimension, position);
-        assert equalsIgnoreMetadata(getCoordinateReferenceSystem(),
-                position.getCoordinateReferenceSystem(), true) : position;
+        assert assertEquals(getCoordinateReferenceSystem(), position.getCoordinateReferenceSystem())
: position;
         for (int i=0; i<dimension; i++) {
             final double value = position.getOrdinate(i);
             final double lower = getLower(i);
@@ -795,8 +798,7 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
         ensureNonNull("envelope", envelope);
         final int dimension = getDimension();
         ensureDimensionMatches("envelope", dimension, envelope);
-        assert equalsIgnoreMetadata(getCoordinateReferenceSystem(),
-                envelope.getCoordinateReferenceSystem(), true) : envelope;
+        assert assertEquals(getCoordinateReferenceSystem(), envelope.getCoordinateReferenceSystem())
: envelope;
         final DirectPosition lowerCorner = envelope.getLowerCorner();
         final DirectPosition upperCorner = envelope.getUpperCorner();
         for (int i=0; i<dimension; i++) {
@@ -930,8 +932,7 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
         ensureNonNull("envelope", envelope);
         final int dimension = getDimension();
         ensureDimensionMatches("envelope", dimension, envelope);
-        assert equalsIgnoreMetadata(getCoordinateReferenceSystem(),
-                envelope.getCoordinateReferenceSystem(), true) : envelope;
+        assert assertEquals(getCoordinateReferenceSystem(), envelope.getCoordinateReferenceSystem())
: envelope;
         final DirectPosition lowerCorner = envelope.getLowerCorner();
         final DirectPosition upperCorner = envelope.getUpperCorner();
         for (int i=0; i<dimension; i++) {
@@ -1021,10 +1022,10 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
      * [-180…180]° range while the later can have a range of thousands of meters.</div>
      *
      * <h4>Coordinate Reference System</h4>
-     * To be considered equal, the two envelopes must have the same {@linkplain #getDimension()
dimension}
-     * and their CRS must be {@linkplain org.apache.sis.util.Utilities#equalsIgnoreMetadata
equals,
-     * ignoring metadata}. If at least one envelope has a null CRS, then the CRS are ignored
and the
-     * coordinate values are compared as if the CRS were equal.
+     * To be considered equal, the two envelopes must have the same {@linkplain #getDimension()
number of dimensions}
+     * and their CRS must be {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATE approximately
equal}.
+     * If at least one envelope has a null CRS, then the CRS are ignored and the coordinate
values are compared
+     * as if the CRS were equal.
      *
      * @param  other          the envelope to compare with.
      * @param  eps            the tolerance value to use for numerical comparisons.
@@ -1038,8 +1039,9 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
     public boolean equals(final Envelope other, final double eps, final boolean epsIsRelative)
{
         ensureNonNull("other", other);
         final int dimension = getDimension();
-        if (other.getDimension() != dimension || !equalsIgnoreMetadata(
-                getCoordinateReferenceSystem(), other.getCoordinateReferenceSystem(), false))
+        if (other.getDimension() != dimension ||
+                !equals(getCoordinateReferenceSystem(), other.getCoordinateReferenceSystem(),
+                        (eps == 0) ? ComparisonMode.IGNORE_METADATA : ComparisonMode.APPROXIMATE))
         {
             return false;
         }
@@ -1062,6 +1064,12 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
         return true;
     }
 
+    /*
+     * Do not implement `LenientComparable.equals(Object, ComparisonMode)` because the choice
of a tolerance
+     * threshold is too arbitrary.  We want users to invoke above `equals(Envelope, double,
boolean)` method
+     * themselves with a tolerance computed for example from the resolution of their data.
+     */
+
     /**
      * Returns {@code true} if the specified object is an envelope of the same class
      * with equals coordinates and {@linkplain #getCoordinateReferenceSystem() CRS}.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
index b017c45..b8b6254 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
@@ -537,7 +537,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable,
Seriali
      * coordinates was {@link Double#NaN} in which case the corresponding coordinate has
been ignored.</p>
      *
      * <h4>Pre-conditions</h4>
-     * This method assumes that the specified point uses the same CRS than this envelope.
+     * This method assumes that the specified point uses a CRS equivalent to this envelope
CRS.
      * For performance reasons, it will no be verified unless Java assertions are enabled.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -561,7 +561,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable,
Seriali
         final int beginIndex = beginIndex();
         final int dimension = endIndex() - beginIndex;
         ensureDimensionMatches("position", dimension, position);
-        assert equalsIgnoreMetadata(crs, position.getCoordinateReferenceSystem(), true) :
position;
+        assert assertEquals(crs, position.getCoordinateReferenceSystem()) : position;
         final int d = coordinates.length >>> 1;
         for (int i=0; i<dimension; i++) {
             final int iLower = beginIndex + i;
@@ -617,7 +617,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable,
Seriali
      * The resulting envelope is the union of the two {@code Envelope} objects.
      *
      * <h4>Pre-conditions</h4>
-     * This method assumes that the specified envelope uses the same CRS than this envelope.
+     * This method assumes that the specified envelope uses a CRS equivalent to this envelope
CRS.
      * For performance reasons, it will no be verified unless Java assertions are enabled.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -665,7 +665,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable,
Seriali
         final int beginIndex = beginIndex();
         final int dimension = endIndex() - beginIndex;
         ensureDimensionMatches("envelope", dimension, envelope);
-        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem(), true) :
envelope;
+        assert assertEquals(crs, envelope.getCoordinateReferenceSystem()) : envelope;
         final DirectPosition lower = envelope.getLowerCorner();
         final DirectPosition upper = envelope.getUpperCorner();
         final int d = coordinates.length >>> 1;
@@ -773,7 +773,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable,
Seriali
      * Sets this envelope to the intersection of this envelope with the specified one.
      *
      * <h4>Pre-conditions</h4>
-     * This method assumes that the specified envelope uses the same CRS than this envelope.
+     * This method assumes that the specified envelope uses a CRS equivalent to this envelope
CRS.
      * For performance reasons, it will no be verified unless Java assertions are enabled.
      *
      * <h4>Crossing the anti-meridian of a Geographic CRS</h4>
@@ -821,7 +821,7 @@ public class GeneralEnvelope extends ArrayEnvelope implements Cloneable,
Seriali
         final int beginIndex = beginIndex();
         final int dimension = endIndex() - beginIndex;
         ensureDimensionMatches("envelope", dimension, envelope);
-        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem(), true) :
envelope;
+        assert assertEquals(crs, envelope.getCoordinateReferenceSystem()) : envelope;
         final DirectPosition lower = envelope.getLowerCorner();
         final DirectPosition upper = envelope.getUpperCorner();
         final int d = coordinates.length >>> 1;


Mime
View raw message