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 2020/03/30 14:01:43 UTC
[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.
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 <ma...@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;