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 fbde8a0a60f164a0dda0a5ecff32083d53d95884 Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri May 27 18:26:48 2022 +0200 Avoid wraparound when the result does not intersect the base grid geometry. The fix use `GridExtent.toEnvelopes(…)` (note the pluarl form) is applied in only once place for now, but we should check if it applies to more places. https://issues.apache.org/jira/browse/SIS-548 --- .../apache/sis/coverage/grid/DomainLinearizer.java | 2 +- .../apache/sis/coverage/grid/GridDerivation.java | 27 ++++- .../org/apache/sis/coverage/grid/GridExtent.java | 119 ++++++++++++++++----- .../org/apache/sis/coverage/grid/GridGeometry.java | 9 +- .../sis/coverage/grid/ResampledGridCoverage.java | 4 +- .../apache/sis/coverage/grid/SliceGeometry.java | 2 +- .../sis/coverage/grid/GridDerivationTest.java | 31 +++++- .../apache/sis/coverage/grid/GridExtentTest.java | 29 ++++- 8 files changed, 179 insertions(+), 44 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java index 73aa001e39..e9f74b6b0b 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java @@ -168,7 +168,7 @@ public class DomainLinearizer { ArgumentChecks.ensureNonNull("gg", gg); if (gg.nonLinears != 0) try { MathTransform gridToCRS = gg.requireGridToCRS(true); - GeneralEnvelope domain = gg.extent.toCRS(null, null, null); + GeneralEnvelope domain = gg.extent.toEnvelope(); MathTransform approximate = modify(LinearTransformBuilder.approximate(gridToCRS, domain)); MathTransform gridToGrid = MathTransforms.concatenate(gridToCRS, approximate.inverse()); domain = Envelopes.transform(gridToGrid, domain); diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java index 44772cba57..e1909ce0bc 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java @@ -79,7 +79,7 @@ import org.opengis.coverage.PointOutsideCoverageException; * * @author Martin Desruisseaux (Geomatys) * @author Alexis Manin (Geomatys) - * @version 1.2 + * @version 1.3 * * @see GridGeometry#derive() * @see GridGeometry#reduce(int...) @@ -510,7 +510,13 @@ public class GridDerivation { finder.nowraparound(); mapCenters = finder.gridToGrid(); // We will use only the scale factors. if (domain != null) { - setBaseExtentClipped(domain.toCRS(mapCorners, mapCenters, null)); + final GeneralEnvelope[] envelopes; + if (mapCorners != null) { + envelopes = domain.toEnvelopes(mapCorners, mapCenters, null); + } else { + envelopes = new GeneralEnvelope[] {domain.toEnvelope()}; + } + setBaseExtentClipped(envelopes); if (baseExtent != base.extent && baseExtent.equals(domain)) { baseExtent = domain; // Share common instance. } @@ -805,8 +811,19 @@ public class GridDerivation { * * @see #getBaseExtentExpanded(boolean) */ - private void setBaseExtentClipped(final GeneralEnvelope indices) { - final GridExtent sub = new GridExtent(indices, rounding, clipping, margin, chunkSize, baseExtent, modifiedDimensions); + private void setBaseExtentClipped(final GeneralEnvelope... indices) { + GridExtent sub = null; + IllegalArgumentException error = null; + for (final GeneralEnvelope ix : indices) try { + final GridExtent c = new GridExtent(ix, rounding, clipping, margin, chunkSize, baseExtent, modifiedDimensions); + sub = (sub == null) ? c : sub.union(c); + } catch (IllegalArgumentException e) { + if (error == null) error = e; + else error.addSuppressed(e); + } + if (sub == null) { + throw error; // Should never be null because `indices` should never be empty. + } if (!sub.equals(baseExtent)) { baseExtent = sub; } @@ -1100,7 +1117,7 @@ public class GridDerivation { * * • x = (h₂ + f) × c⋅s where h₂ = floor(h₁/s) and f = ((h₁ mod s) + 1)/s * - * Because s ≥ 1, then f ≤ 1. But the f value actually used by GridExtent.toCRS(…) is hard-coded to 1 + * Because s ≥ 1, then f ≤ 1. But the f value actually used by GridExtent.toEnvelope(…) is hard-coded to 1 * since it assumes that all cells are whole, i.e. it does not take in account that the last cell may * actually be fraction of a cell. Since 1 ≥ f, the computed envelope may be larger. This explains the * need for envelope clipping performed by GridGeometry constructor. 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 3376c00fe0..37822acd1d 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 @@ -392,7 +392,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable * This argument is ignored if {@code enclosing} is null. * @throws DisjointExtentException if the given envelope does not intersect the enclosing grid extent. * - * @see #toCRS(MathTransform, MathTransform, Envelope) + * @see #toEnvelope(MathTransform, MathTransform, Envelope) * @see #slice(DirectPosition, int[]) */ GridExtent(final AbstractEnvelope envelope, final GridRoundingMode rounding, final GridClippingMode clipping, @@ -420,12 +420,12 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable final boolean isMaxValid = (max <= Long.MAX_VALUE); if (min > max || (enclosing == null && !(isMinValid & isMaxValid))) { /* - * We do not throw an exception if `enclosing` is non-null and envelope bounds are NaN - * because this case occurs when the gridToCRS transform has a NaN scale factor. Such - * scale factor may occur with ranges like [0 … 0]. With a non-null `enclosing` extent, - * we can still have grid coordinates: they are inherited from `enclosing`. We require - * the two bounds to be NaN, otherwise the reason for those NaN envelope bounds is not - * a NaN scale factor. + * We do not throw an exception for NaN envelope bounds if `enclosing` is non-null + * because this case occurs when the `gridToCRS` transform has a NaN scale factor. + * Such scale factor may result from ranges like [0 … 0]. We tolerate them because + * with a non-null `enclosing` extent, we can still have grid coordinates: they are + * inherited from `enclosing`. Note that we require the two bounds to be NaN, because + * otherwise the reason for those NaN envelope bounds is not a NaN scale factor. */ throw new IllegalArgumentException(Resources.format( Resources.Keys.IllegalGridEnvelope_3, getAxisIdentification(i,i), min, max)); @@ -999,7 +999,7 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable */ public GeneralEnvelope toEnvelope(final MathTransform cornerToCRS) throws TransformException { ArgumentChecks.ensureNonNull("cornerToCRS", cornerToCRS); - final GeneralEnvelope envelope = toCRS(cornerToCRS, cornerToCRS, null); + final GeneralEnvelope envelope = toEnvelope(cornerToCRS, cornerToCRS, null); final Matrix gridToCRS = MathTransforms.getMatrix(cornerToCRS); if (gridToCRS != null && Matrices.isAffine(gridToCRS)) try { envelope.setCoordinateReferenceSystem(GridExtentCRS.build(gridToCRS, (types != null) ? types : DEFAULT_TYPES, null)); @@ -1014,10 +1014,10 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable * The transform shall map <em>cell corner</em> to real world coordinates. * This method does not set the envelope coordinate reference system. * - * @param cornerToCRS a transform from <em>cell corners</em> to real world coordinates, or {@code null} if none. + * @param cornerToCRS a transform from <em>cell corners</em> to real world coordinates. * @param gridToCRS the transform specified by the user. May be the same as {@code cornerToCRS}. * If different, then this is assumed to map cell centers instead of cell corners. - * @param fallback bounds to use if some values still NaN, or {@code null} if none. + * @param fallback bounds to use if some values are still NaN after conversion, or {@code null} if none. * @return this grid extent in real world coordinates. * @throws TransformException if the envelope can not be computed with the given transform. * @@ -1025,28 +1025,70 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable * * @see GridGeometry#getEnvelope(CoordinateReferenceSystem) */ - final GeneralEnvelope toCRS(final MathTransform cornerToCRS, final MathTransform gridToCRS, final Envelope fallback) + final GeneralEnvelope toEnvelope(final MathTransform cornerToCRS, final MathTransform gridToCRS, final Envelope fallback) throws TransformException { + final GeneralEnvelope envelope = Envelopes.transform(cornerToCRS, toEnvelope()); + complete(envelope, gridToCRS, gridToCRS != cornerToCRS, fallback); + return envelope; + } + + /** + * Returns the coordinates of this grid extent in an envelope. + * The returned envelope has no CRS. + */ + final GeneralEnvelope toEnvelope() { final int dimension = getDimension(); - GeneralEnvelope envelope = new GeneralEnvelope(dimension); + final GeneralEnvelope envelope = new GeneralEnvelope(dimension); for (int i=0; i<dimension; i++) { long high = coordinates[i + dimension]; if (high != Long.MAX_VALUE) high++; // Make the coordinate exclusive before cast. envelope.setRange(i, coordinates[i], high); // Possible loss of precision in cast to `double` type. } - if (cornerToCRS == null) { - return envelope; + return envelope; + } + + /** + * Transforms this grid extent to "real world" envelopes using the given transform. + * This method usually returns exactly one envelope, but may return more envelopes if the given transform + * contains at least one {@link org.apache.sis.referencing.operation.transform.WraparoundTransform} step. + * + * @param cornerToCRS a transform from <em>cell corners</em> to real world coordinates. + * @param gridToCRS the transform specified by the user. May be the same as {@code cornerToCRS}. + * If different, then this is assumed to map cell centers instead of cell corners. + * @param fallback bounds to use if some values are still NaN after conversion, or {@code null} if none. + * @return this grid extent in real world coordinates. + * @throws TransformException if the envelope can not be computed with the given transform. + * + * @see #GridExtent(AbstractEnvelope, GridRoundingMode, int[], GridExtent, int[]) + * + * @see GridGeometry#getEnvelope(CoordinateReferenceSystem) + */ + final GeneralEnvelope[] toEnvelopes(final MathTransform cornerToCRS, final MathTransform gridToCRS, final Envelope fallback) + throws TransformException + { + final GeneralEnvelope[] envelopes = Envelopes.transformWraparounds(cornerToCRS, toEnvelope()); + for (final GeneralEnvelope envelope : envelopes) { + complete(envelope, gridToCRS, gridToCRS != cornerToCRS, fallback); } - envelope = Envelopes.transform(cornerToCRS, envelope); + return envelopes; + } + + /** + * If the envelope contains some NaN values, tries to replace them by constant values inferred from the math transform. + * We must use the {@link MathTransform} specified by the user ({@code gridToCRS}), not necessarily {@code cornerToCRS}, + * because inferring a {@code cornerToCRS} by translating a {@code centerToCRS} by 0.5 cell increase the amount of NaN + * values in the matrix. For giving a chance to {@link TransformSeparator} to perform its work, + * we need the minimal amount of NaN values. + * + * @param envelope the envelope to complete if empty. + * @param gridToCRS the transform specified by user. + * @param isCenter whether the "grid to CRS" transform maps cell center instead of cell corners. + * @param fallback bounds to use if some values are still NaN after conversion, or {@code null} if none. + */ + private void complete(final GeneralEnvelope envelope, final MathTransform gridToCRS, final boolean isCenter, final Envelope fallback) { if (envelope.isEmpty()) try { - /* - * If the envelope contains some NaN values, try to replace them by constant values inferred from the math transform. - * We must use the MathTransform specified by the user (gridToCRS), not necessarily the cornerToCRS transform, because - * inferring a `cornerToCRS` by translating a `centerToCRS` by 0.5 cell increase the amount of NaN values in the matrix. - * For giving a chance to TransformSeparator to perform its work, we need the minimal amount of NaN values. - */ - final boolean isCenter = (gridToCRS != cornerToCRS); + final int dimension = getDimension(); TransformSeparator separator = null; for (int srcDim=0; srcDim < dimension; srcDim++) { if (coordinates[srcDim + dimension] == 0 && coordinates[srcDim] == 0) { @@ -1115,7 +1157,6 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable // "toEnvelope" is the closest public method that may invoke this method. Logging.recoverableException(getLogger(Modules.RASTER), GridExtent.class, "toEnvelope", e); } - return envelope; } /** @@ -1588,17 +1629,45 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable * @return the intersection result. May be one of the existing instances. */ final GridExtent intersect(final GridExtent other) { + return combine(other, false); + } + + /** + * Returns the union of this grid extent with to the given grid extent. + * The given extent shall have the same number of dimensions. + * + * <p>This method is not public because we do not yet have a policy + * about whether we should verify if axis {@link #types} match.</p> + * + * @param other the grid to combine with. + * @return the union result. May be one of the existing instances. + */ + final GridExtent union(final GridExtent other) { + return combine(other, true); + } + + /** + * Implementation of {@link #union(GridExtent)} and {@link #intersect(GridExtent)} + */ + private GridExtent combine(final GridExtent other, final boolean union) { final int n = coordinates.length; final int m = n >>> 1; final long[] clipped = new long[n]; int i = 0; - while (i < m) {clipped[i] = Math.max(coordinates[i], other.coordinates[i]); i++;} - while (i < n) {clipped[i] = Math.min(coordinates[i], other.coordinates[i]); i++;} + while (i < m) {clipped[i] = extremum(coordinates[i], other.coordinates[i], !union); i++;} + while (i < n) {clipped[i] = extremum(coordinates[i], other.coordinates[i], union); i++;} if (Arrays.equals(clipped, this.coordinates)) return this; if (Arrays.equals(clipped, other.coordinates)) return other; return new GridExtent(this, clipped); } + /** + * Returns the minimum or maximum value between the given pair of values. + */ + private static long extremum(final long a, final long b, final boolean max) { + return max ? Math.max(a, b) : Math.min(a, b); + } + /** * Returns a hash value for this grid extent. This value needs not to remain * consistent between different implementations of the same class. 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 7d7c14fe8c..42613d6ce3 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 @@ -465,7 +465,7 @@ public class GridGeometry implements LenientComparable, Serializable { { final GeneralEnvelope env; if (extent != null && cornerToCRS != null) { - env = extent.toCRS(cornerToCRS, specified, limits); + env = extent.toEnvelope(cornerToCRS, specified, limits); env.setCoordinateReferenceSystem(crs); if (limits != null) { env.intersect(limits); @@ -533,7 +533,8 @@ public class GridGeometry implements LenientComparable, Serializable { try { env = Envelopes.transform(cornerToCRS.inverse(), envelope); extent = new GridExtent(env, rounding, GridClippingMode.STRICT, null, null, null, null); - env = extent.toCRS(cornerToCRS, gridToCRS, envelope); // `gridToCRS` specified by the user, not `this.gridToCRS`. + env = extent.toEnvelope(cornerToCRS, gridToCRS, envelope); + // Use `gridToCRS` specified by the user, not `this.gridToCRS`. } catch (TransformException e) { throw new IllegalGridGeometryException(e, "gridToCRS"); } @@ -934,7 +935,7 @@ public class GridGeometry implements LenientComparable, Serializable { clip = null; } MathTransform tr = MathTransforms.concatenate(cornerToCRS, op.getMathTransform()); - final GeneralEnvelope env = extent.toCRS(tr, tr, clip); + final GeneralEnvelope env = extent.toEnvelope(tr, tr, clip); env.setCoordinateReferenceSystem(op.getTargetCRS()); env.normalize(); if (clip != null) { @@ -1404,7 +1405,7 @@ public class GridGeometry implements LenientComparable, Serializable { ensureDimensionMatches(getDimension(), extent); final ImmutableEnvelope relocated; if (cornerToCRS != null) { - final GeneralEnvelope env = extent.toCRS(cornerToCRS, gridToCRS, null); + final GeneralEnvelope env = extent.toEnvelope(cornerToCRS, gridToCRS, null); env.setCoordinateReferenceSystem(getCoordinateReferenceSystem(envelope)); relocated = new ImmutableEnvelope(env); } else { 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 6df6d5d925..47cae92677 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 @@ -475,7 +475,7 @@ final class ResampledGridCoverage extends GridCoverage { final MathTransform crsToGrid, final boolean center) throws TransformException { final MathTransform sourceToTarget = MathTransforms.concatenate(cornerToCRS, crsToGrid); - final GeneralEnvelope bounds = source.toCRS(sourceToTarget, sourceToTarget, null); + final GeneralEnvelope bounds = source.toEnvelope(sourceToTarget, sourceToTarget, null); if (center) { final double[] vector = new double[bounds.getDimension()]; Arrays.fill(vector, 0.5); @@ -507,7 +507,7 @@ final class ResampledGridCoverage extends GridCoverage { * If a dimension can not be converted (e.g. because a `gridToCRS` transform has a NaN * factor in that dimension), the corresponding source grid coordinates will be copied. */ - final GeneralEnvelope sourceBounds = sliceExtent.toCRS(toSourceCorner, toSourceCenter, null); + final GeneralEnvelope sourceBounds = sliceExtent.toEnvelope(toSourceCorner, toSourceCenter, null); final int dimension = sourceBounds.getDimension(); if (sourceBounds.isEmpty()) { final GridExtent se = source.gridGeometry.getExtent(); diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java index 18e56c8b0e..45276f1598 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java @@ -181,7 +181,7 @@ final class SliceGeometry implements Function<RenderedImage, GridGeometry> { } GeneralEnvelope subArea = null; if (useSubExtent && cornerToCRS != null) try { - subArea = extent.toCRS(cornerToCRS, gridToCRS, null); + subArea = extent.toEnvelope(cornerToCRS, gridToCRS, null); } catch (TransformException e) { // GridGeometry.reduce(…) is the public method invoking indirectly this method. GridGeometry.recoverableException("reduce", e); diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java index 54d27cf404..f13e80d1c9 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java @@ -56,7 +56,7 @@ import static org.apache.sis.coverage.grid.GridGeometryTest.assertExtentEquals; * @author Martin Desruisseaux (Geomatys) * @author Alexis Manin (Geomatys) * @author Johann Sorel (Geomatys) - * @version 1.2 + * @version 1.3 * @since 1.0 * @module */ @@ -628,6 +628,35 @@ public final strictfp class GridDerivationTest extends TestCase { */ @Test public void testAntiMeridianCrossingInSubgrid() { + final GridExtent be = new GridExtent(null, new long[] {0, -64800}, new long[] {168000, 21600}, false); + final GridExtent qe = new GridExtent(256, 256); + final double bs = 350d / 168000; // We will use [-175 … 175]° of longitude. + final double qs = 10d / 256; // We will use [-182 … -172]° of longitude. + final AffineTransform2D bt = new AffineTransform2D(bs, 0, 0, -bs, -175, -45); + final AffineTransform2D qt = new AffineTransform2D(qs, 0, 0, -qs, -182, 90); + final GridGeometry base = new GridGeometry(be, PixelInCell.CELL_CORNER, bt, HardCodedCRS.WGS84); + final GridGeometry query = new GridGeometry(qe, PixelInCell.CELL_CORNER, qt, HardCodedCRS.WGS84); + /* + * The [-182 … -172]° longitude range intersects [-175 … 175]° only in the [-175 … -172]° part. + * The [-182 … -175]° part becomes [178 … 185]° after wraparound, which still outside the base + * and should not be included in the intersection result. + */ + GridGeometry result = base.derive().subgrid(query).build(); + assertExtentEquals(new long[] {0, -3410}, new long[] {75, -3158}, result.getExtent()); + assertEnvelopeEquals(new Envelope2D(null, + -175, // Expected minimum value. + 80, // Not interresting for this test. + -172 - -175, // Expected maximum value minus minimum. + 90 - 80), + result.getEnvelope(), 0.02); + } + + /** + * Tests deriving a grid geometry with a request crossing the antimeridian. + * The request uses an envelope instead of a {@link GridGeometry}. + */ + @Test + public void testAntiMeridianCrossingInEnvelope() { final GridGeometry grid = new GridGeometry( new GridExtent(200, 180), PixelInCell.CELL_CORNER, MathTransforms.linear(new Matrix3( diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java index 040ecc36e3..5bbdbc32ff 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java @@ -245,22 +245,41 @@ public final strictfp class GridExtentTest extends TestCase { assertFalse(extent.contains(100, 200, 39)); } + /** + * Creates another arbitrary extent for tests of union and intersection. + */ + private static GridExtent createOther() { + return new GridExtent( + new DimensionNameType[] {DimensionNameType.COLUMN, DimensionNameType.ROW, DimensionNameType.TIME}, + new long[] {150, 220, 35}, new long[] {400, 820, 47}, false); + } + /** * Tests {@link GridExtent#intersect(GridExtent)}. */ @Test public void testIntersect() { - final GridExtent domain = new GridExtent( - new DimensionNameType[] {DimensionNameType.COLUMN, DimensionNameType.ROW, DimensionNameType.TIME}, - new long[] {150, 220, 35}, new long[] {400, 820, 47}, false); - GridExtent extent = create3D(); - extent = extent.intersect(domain); + final GridExtent domain = createOther(); + final GridExtent extent = create3D().intersect(domain); assertExtentEquals(extent, 0, 150, 399); assertExtentEquals(extent, 1, 220, 799); assertExtentEquals(extent, 2, 40, 46); assertSame(extent.intersect(domain), extent); } + /** + * Tests {@link GridExtent#union(GridExtent)}. + */ + @Test + public void testUnion() { + final GridExtent domain = createOther(); + final GridExtent extent = create3D().union(domain); + assertExtentEquals(extent, 0, 100, 499); + assertExtentEquals(extent, 1, 200, 819); + assertExtentEquals(extent, 2, 35, 49); + assertSame(extent.union(domain), extent); + } + /** * Tests {@link GridExtent#slice(DirectPosition, int[])}. */
